authentik.providers.oauth2.tests.test_end_session
Test OAuth2 End Session (RP-Initiated Logout) implementation
1"""Test OAuth2 End Session (RP-Initiated Logout) implementation""" 2 3from django.test import RequestFactory 4from django.urls import reverse 5 6from authentik.core.models import Application 7from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow 8from authentik.lib.generators import generate_id 9from authentik.providers.oauth2.models import ( 10 OAuth2Provider, 11 RedirectURI, 12 RedirectURIMatchingMode, 13 RedirectURIType, 14) 15from authentik.providers.oauth2.tests.utils import OAuthTestCase 16from authentik.providers.oauth2.views.end_session import EndSessionView 17 18 19class TestEndSessionView(OAuthTestCase): 20 """Test EndSessionView validation""" 21 22 def setUp(self) -> None: 23 super().setUp() 24 self.user = create_test_admin_user() 25 self.invalidation_flow = create_test_flow() 26 self.app = Application.objects.create(name=generate_id(), slug="test-app") 27 self.provider = OAuth2Provider.objects.create( 28 name=generate_id(), 29 authorization_flow=create_test_flow(), 30 invalidation_flow=self.invalidation_flow, 31 redirect_uris=[ 32 RedirectURI( 33 RedirectURIMatchingMode.STRICT, 34 "http://testserver/callback", 35 RedirectURIType.AUTHORIZATION, 36 ), 37 RedirectURI( 38 RedirectURIMatchingMode.STRICT, 39 "http://testserver/logout", 40 RedirectURIType.LOGOUT, 41 ), 42 RedirectURI( 43 RedirectURIMatchingMode.REGEX, 44 r"https://.*\.example\.com/logout", 45 RedirectURIType.LOGOUT, 46 ), 47 ], 48 ) 49 self.app.provider = self.provider 50 self.app.save() 51 # Ensure brand has an invalidation flow 52 self.brand = create_test_brand() 53 self.brand.flow_invalidation = self.invalidation_flow 54 self.brand.save() 55 56 def _id_token_hint(self, host: str) -> str: 57 """Issue a valid id_token_hint for the test provider under the given host.""" 58 return self.provider.encode( 59 { 60 "iss": f"http://{host}/application/o/{self.app.slug}/", 61 "aud": self.provider.client_id, 62 "sub": str(self.user.pk), 63 } 64 ) 65 66 def test_post_logout_redirect_uri_strict_match(self): 67 """Test strict URI matching redirects to flow""" 68 self.client.force_login(self.user) 69 response = self.client.get( 70 reverse( 71 "authentik_providers_oauth2:end-session", 72 kwargs={"application_slug": self.app.slug}, 73 ), 74 { 75 "post_logout_redirect_uri": "http://testserver/logout", 76 "id_token_hint": self._id_token_hint(self.brand.domain), 77 }, 78 HTTP_HOST=self.brand.domain, 79 ) 80 # Should redirect to the invalidation flow 81 self.assertEqual(response.status_code, 302) 82 self.assertIn(self.invalidation_flow.slug, response.url) 83 84 def test_post_logout_redirect_uri_strict_no_match(self): 85 """Test strict URI not matching returns an error and does not start logout flow. 86 87 Required by OIDC RP-Initiated Logout 1.0: on an unregistered 88 post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with 89 logout that targets the RP. 90 """ 91 self.client.force_login(self.user) 92 invalid_uri = "http://testserver/other" 93 response = self.client.get( 94 reverse( 95 "authentik_providers_oauth2:end-session", 96 kwargs={"application_slug": self.app.slug}, 97 ), 98 { 99 "post_logout_redirect_uri": invalid_uri, 100 "id_token_hint": self._id_token_hint(self.brand.domain), 101 }, 102 HTTP_HOST=self.brand.domain, 103 ) 104 self.assertEqual(response.status_code, 400) 105 self.assertNotIn(invalid_uri, response.content.decode()) 106 107 def test_post_logout_redirect_uri_regex_match(self): 108 """Test regex URI matching redirects to flow""" 109 self.client.force_login(self.user) 110 response = self.client.get( 111 reverse( 112 "authentik_providers_oauth2:end-session", 113 kwargs={"application_slug": self.app.slug}, 114 ), 115 { 116 "post_logout_redirect_uri": "https://app.example.com/logout", 117 "id_token_hint": self._id_token_hint(self.brand.domain), 118 }, 119 HTTP_HOST=self.brand.domain, 120 ) 121 # Should redirect to the invalidation flow 122 self.assertEqual(response.status_code, 302) 123 self.assertIn(self.invalidation_flow.slug, response.url) 124 125 def test_post_logout_redirect_uri_regex_no_match(self): 126 """Test regex URI not matching returns an error and does not start logout flow.""" 127 self.client.force_login(self.user) 128 invalid_uri = "https://malicious.com/logout" 129 response = self.client.get( 130 reverse( 131 "authentik_providers_oauth2:end-session", 132 kwargs={"application_slug": self.app.slug}, 133 ), 134 { 135 "post_logout_redirect_uri": invalid_uri, 136 "id_token_hint": self._id_token_hint(self.brand.domain), 137 }, 138 HTTP_HOST=self.brand.domain, 139 ) 140 self.assertEqual(response.status_code, 400) 141 self.assertNotIn(invalid_uri, response.content.decode()) 142 143 def test_state_parameter_appended_to_uri(self): 144 """Test state parameter is appended to validated redirect URI""" 145 factory = RequestFactory() 146 request = factory.get( 147 "/end-session/", 148 { 149 "post_logout_redirect_uri": "http://testserver/logout", 150 "state": "test-state-123", 151 "id_token_hint": self._id_token_hint("testserver"), 152 }, 153 ) 154 request.user = self.user 155 request.brand = self.brand 156 157 view = EndSessionView() 158 view.request = request 159 view.kwargs = {"application_slug": self.app.slug} 160 view.resolve_provider_application() 161 view.validate() 162 163 self.assertIn("state=test-state-123", view.post_logout_redirect_uri) 164 165 def test_post_method(self): 166 """Test POST requests work same as GET""" 167 self.client.force_login(self.user) 168 response = self.client.post( 169 reverse( 170 "authentik_providers_oauth2:end-session", 171 kwargs={"application_slug": self.app.slug}, 172 ), 173 { 174 "post_logout_redirect_uri": "http://testserver/logout", 175 "state": "xyz789", 176 "id_token_hint": self._id_token_hint(self.brand.domain), 177 }, 178 HTTP_HOST=self.brand.domain, 179 ) 180 self.assertEqual(response.status_code, 302) 181 182 183class TestEndSessionAPI(OAuthTestCase): 184 """Test End Session API functionality""" 185 186 def setUp(self) -> None: 187 super().setUp() 188 self.user = create_test_admin_user() 189 self.client.force_login(self.user) 190 191 def test_post_logout_redirect_uris_create(self): 192 """Test creating provider with post_logout redirect_uris""" 193 response = self.client.post( 194 reverse("authentik_api:oauth2provider-list"), 195 data={ 196 "name": generate_id(), 197 "authorization_flow": create_test_flow().pk, 198 "invalidation_flow": create_test_flow().pk, 199 "redirect_uris": [ 200 { 201 "matching_mode": "strict", 202 "url": "http://testserver/callback", 203 "redirect_uri_type": "authorization", 204 }, 205 { 206 "matching_mode": "strict", 207 "url": "http://testserver/logout", 208 "redirect_uri_type": "logout", 209 }, 210 { 211 "matching_mode": "regex", 212 "url": "https://.*\\.example\\.com/logout", 213 "redirect_uri_type": "logout", 214 }, 215 ], 216 }, 217 content_type="application/json", 218 ) 219 self.assertEqual(response.status_code, 201) 220 provider_data = response.json() 221 post_logout_uris = [ 222 u for u in provider_data["redirect_uris"] if u["redirect_uri_type"] == "logout" 223 ] 224 self.assertEqual(len(post_logout_uris), 2) 225 226 def test_post_logout_redirect_uris_invalid_regex(self): 227 """Test that invalid regex patterns are rejected""" 228 response = self.client.post( 229 reverse("authentik_api:oauth2provider-list"), 230 data={ 231 "name": generate_id(), 232 "authorization_flow": create_test_flow().pk, 233 "invalidation_flow": create_test_flow().pk, 234 "redirect_uris": [ 235 { 236 "matching_mode": "strict", 237 "url": "http://testserver/callback", 238 "redirect_uri_type": "authorization", 239 }, 240 { 241 "matching_mode": "regex", 242 "url": "**invalid**", 243 "redirect_uri_type": "logout", 244 }, 245 ], 246 }, 247 content_type="application/json", 248 ) 249 self.assertEqual(response.status_code, 400) 250 self.assertIn("redirect_uris", response.json()) 251 252 def test_post_logout_redirect_uris_update(self): 253 """Test updating redirect_uris with logout type""" 254 # First create a provider 255 provider = OAuth2Provider.objects.create( 256 name=generate_id(), 257 authorization_flow=create_test_flow(), 258 redirect_uris=[ 259 RedirectURI( 260 RedirectURIMatchingMode.STRICT, 261 "http://testserver/callback", 262 RedirectURIType.AUTHORIZATION, 263 ), 264 ], 265 ) 266 267 # Update with post_logout redirect URIs 268 response = self.client.patch( 269 reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider.pk}), 270 data={ 271 "redirect_uris": [ 272 { 273 "matching_mode": "strict", 274 "url": "http://testserver/callback", 275 "redirect_uri_type": "authorization", 276 }, 277 { 278 "matching_mode": "strict", 279 "url": "http://testserver/logout", 280 "redirect_uri_type": "logout", 281 }, 282 ], 283 }, 284 content_type="application/json", 285 ) 286 self.assertEqual(response.status_code, 200) 287 288 # Verify the update 289 provider.refresh_from_db() 290 self.assertEqual(len(provider.post_logout_redirect_uris), 1) 291 self.assertEqual(provider.post_logout_redirect_uris[0].url, "http://testserver/logout")
20class TestEndSessionView(OAuthTestCase): 21 """Test EndSessionView validation""" 22 23 def setUp(self) -> None: 24 super().setUp() 25 self.user = create_test_admin_user() 26 self.invalidation_flow = create_test_flow() 27 self.app = Application.objects.create(name=generate_id(), slug="test-app") 28 self.provider = OAuth2Provider.objects.create( 29 name=generate_id(), 30 authorization_flow=create_test_flow(), 31 invalidation_flow=self.invalidation_flow, 32 redirect_uris=[ 33 RedirectURI( 34 RedirectURIMatchingMode.STRICT, 35 "http://testserver/callback", 36 RedirectURIType.AUTHORIZATION, 37 ), 38 RedirectURI( 39 RedirectURIMatchingMode.STRICT, 40 "http://testserver/logout", 41 RedirectURIType.LOGOUT, 42 ), 43 RedirectURI( 44 RedirectURIMatchingMode.REGEX, 45 r"https://.*\.example\.com/logout", 46 RedirectURIType.LOGOUT, 47 ), 48 ], 49 ) 50 self.app.provider = self.provider 51 self.app.save() 52 # Ensure brand has an invalidation flow 53 self.brand = create_test_brand() 54 self.brand.flow_invalidation = self.invalidation_flow 55 self.brand.save() 56 57 def _id_token_hint(self, host: str) -> str: 58 """Issue a valid id_token_hint for the test provider under the given host.""" 59 return self.provider.encode( 60 { 61 "iss": f"http://{host}/application/o/{self.app.slug}/", 62 "aud": self.provider.client_id, 63 "sub": str(self.user.pk), 64 } 65 ) 66 67 def test_post_logout_redirect_uri_strict_match(self): 68 """Test strict URI matching redirects to flow""" 69 self.client.force_login(self.user) 70 response = self.client.get( 71 reverse( 72 "authentik_providers_oauth2:end-session", 73 kwargs={"application_slug": self.app.slug}, 74 ), 75 { 76 "post_logout_redirect_uri": "http://testserver/logout", 77 "id_token_hint": self._id_token_hint(self.brand.domain), 78 }, 79 HTTP_HOST=self.brand.domain, 80 ) 81 # Should redirect to the invalidation flow 82 self.assertEqual(response.status_code, 302) 83 self.assertIn(self.invalidation_flow.slug, response.url) 84 85 def test_post_logout_redirect_uri_strict_no_match(self): 86 """Test strict URI not matching returns an error and does not start logout flow. 87 88 Required by OIDC RP-Initiated Logout 1.0: on an unregistered 89 post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with 90 logout that targets the RP. 91 """ 92 self.client.force_login(self.user) 93 invalid_uri = "http://testserver/other" 94 response = self.client.get( 95 reverse( 96 "authentik_providers_oauth2:end-session", 97 kwargs={"application_slug": self.app.slug}, 98 ), 99 { 100 "post_logout_redirect_uri": invalid_uri, 101 "id_token_hint": self._id_token_hint(self.brand.domain), 102 }, 103 HTTP_HOST=self.brand.domain, 104 ) 105 self.assertEqual(response.status_code, 400) 106 self.assertNotIn(invalid_uri, response.content.decode()) 107 108 def test_post_logout_redirect_uri_regex_match(self): 109 """Test regex URI matching redirects to flow""" 110 self.client.force_login(self.user) 111 response = self.client.get( 112 reverse( 113 "authentik_providers_oauth2:end-session", 114 kwargs={"application_slug": self.app.slug}, 115 ), 116 { 117 "post_logout_redirect_uri": "https://app.example.com/logout", 118 "id_token_hint": self._id_token_hint(self.brand.domain), 119 }, 120 HTTP_HOST=self.brand.domain, 121 ) 122 # Should redirect to the invalidation flow 123 self.assertEqual(response.status_code, 302) 124 self.assertIn(self.invalidation_flow.slug, response.url) 125 126 def test_post_logout_redirect_uri_regex_no_match(self): 127 """Test regex URI not matching returns an error and does not start logout flow.""" 128 self.client.force_login(self.user) 129 invalid_uri = "https://malicious.com/logout" 130 response = self.client.get( 131 reverse( 132 "authentik_providers_oauth2:end-session", 133 kwargs={"application_slug": self.app.slug}, 134 ), 135 { 136 "post_logout_redirect_uri": invalid_uri, 137 "id_token_hint": self._id_token_hint(self.brand.domain), 138 }, 139 HTTP_HOST=self.brand.domain, 140 ) 141 self.assertEqual(response.status_code, 400) 142 self.assertNotIn(invalid_uri, response.content.decode()) 143 144 def test_state_parameter_appended_to_uri(self): 145 """Test state parameter is appended to validated redirect URI""" 146 factory = RequestFactory() 147 request = factory.get( 148 "/end-session/", 149 { 150 "post_logout_redirect_uri": "http://testserver/logout", 151 "state": "test-state-123", 152 "id_token_hint": self._id_token_hint("testserver"), 153 }, 154 ) 155 request.user = self.user 156 request.brand = self.brand 157 158 view = EndSessionView() 159 view.request = request 160 view.kwargs = {"application_slug": self.app.slug} 161 view.resolve_provider_application() 162 view.validate() 163 164 self.assertIn("state=test-state-123", view.post_logout_redirect_uri) 165 166 def test_post_method(self): 167 """Test POST requests work same as GET""" 168 self.client.force_login(self.user) 169 response = self.client.post( 170 reverse( 171 "authentik_providers_oauth2:end-session", 172 kwargs={"application_slug": self.app.slug}, 173 ), 174 { 175 "post_logout_redirect_uri": "http://testserver/logout", 176 "state": "xyz789", 177 "id_token_hint": self._id_token_hint(self.brand.domain), 178 }, 179 HTTP_HOST=self.brand.domain, 180 ) 181 self.assertEqual(response.status_code, 302)
Test EndSessionView validation
23 def setUp(self) -> None: 24 super().setUp() 25 self.user = create_test_admin_user() 26 self.invalidation_flow = create_test_flow() 27 self.app = Application.objects.create(name=generate_id(), slug="test-app") 28 self.provider = OAuth2Provider.objects.create( 29 name=generate_id(), 30 authorization_flow=create_test_flow(), 31 invalidation_flow=self.invalidation_flow, 32 redirect_uris=[ 33 RedirectURI( 34 RedirectURIMatchingMode.STRICT, 35 "http://testserver/callback", 36 RedirectURIType.AUTHORIZATION, 37 ), 38 RedirectURI( 39 RedirectURIMatchingMode.STRICT, 40 "http://testserver/logout", 41 RedirectURIType.LOGOUT, 42 ), 43 RedirectURI( 44 RedirectURIMatchingMode.REGEX, 45 r"https://.*\.example\.com/logout", 46 RedirectURIType.LOGOUT, 47 ), 48 ], 49 ) 50 self.app.provider = self.provider 51 self.app.save() 52 # Ensure brand has an invalidation flow 53 self.brand = create_test_brand() 54 self.brand.flow_invalidation = self.invalidation_flow 55 self.brand.save()
Hook method for setting up the test fixture before exercising it.
67 def test_post_logout_redirect_uri_strict_match(self): 68 """Test strict URI matching redirects to flow""" 69 self.client.force_login(self.user) 70 response = self.client.get( 71 reverse( 72 "authentik_providers_oauth2:end-session", 73 kwargs={"application_slug": self.app.slug}, 74 ), 75 { 76 "post_logout_redirect_uri": "http://testserver/logout", 77 "id_token_hint": self._id_token_hint(self.brand.domain), 78 }, 79 HTTP_HOST=self.brand.domain, 80 ) 81 # Should redirect to the invalidation flow 82 self.assertEqual(response.status_code, 302) 83 self.assertIn(self.invalidation_flow.slug, response.url)
Test strict URI matching redirects to flow
85 def test_post_logout_redirect_uri_strict_no_match(self): 86 """Test strict URI not matching returns an error and does not start logout flow. 87 88 Required by OIDC RP-Initiated Logout 1.0: on an unregistered 89 post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with 90 logout that targets the RP. 91 """ 92 self.client.force_login(self.user) 93 invalid_uri = "http://testserver/other" 94 response = self.client.get( 95 reverse( 96 "authentik_providers_oauth2:end-session", 97 kwargs={"application_slug": self.app.slug}, 98 ), 99 { 100 "post_logout_redirect_uri": invalid_uri, 101 "id_token_hint": self._id_token_hint(self.brand.domain), 102 }, 103 HTTP_HOST=self.brand.domain, 104 ) 105 self.assertEqual(response.status_code, 400) 106 self.assertNotIn(invalid_uri, response.content.decode())
Test strict URI not matching returns an error and does not start logout flow.
Required by OIDC RP-Initiated Logout 1.0: on an unregistered post_logout_redirect_uri, the OP MUST NOT redirect and MUST NOT proceed with logout that targets the RP.
108 def test_post_logout_redirect_uri_regex_match(self): 109 """Test regex URI matching redirects to flow""" 110 self.client.force_login(self.user) 111 response = self.client.get( 112 reverse( 113 "authentik_providers_oauth2:end-session", 114 kwargs={"application_slug": self.app.slug}, 115 ), 116 { 117 "post_logout_redirect_uri": "https://app.example.com/logout", 118 "id_token_hint": self._id_token_hint(self.brand.domain), 119 }, 120 HTTP_HOST=self.brand.domain, 121 ) 122 # Should redirect to the invalidation flow 123 self.assertEqual(response.status_code, 302) 124 self.assertIn(self.invalidation_flow.slug, response.url)
Test regex URI matching redirects to flow
126 def test_post_logout_redirect_uri_regex_no_match(self): 127 """Test regex URI not matching returns an error and does not start logout flow.""" 128 self.client.force_login(self.user) 129 invalid_uri = "https://malicious.com/logout" 130 response = self.client.get( 131 reverse( 132 "authentik_providers_oauth2:end-session", 133 kwargs={"application_slug": self.app.slug}, 134 ), 135 { 136 "post_logout_redirect_uri": invalid_uri, 137 "id_token_hint": self._id_token_hint(self.brand.domain), 138 }, 139 HTTP_HOST=self.brand.domain, 140 ) 141 self.assertEqual(response.status_code, 400) 142 self.assertNotIn(invalid_uri, response.content.decode())
Test regex URI not matching returns an error and does not start logout flow.
144 def test_state_parameter_appended_to_uri(self): 145 """Test state parameter is appended to validated redirect URI""" 146 factory = RequestFactory() 147 request = factory.get( 148 "/end-session/", 149 { 150 "post_logout_redirect_uri": "http://testserver/logout", 151 "state": "test-state-123", 152 "id_token_hint": self._id_token_hint("testserver"), 153 }, 154 ) 155 request.user = self.user 156 request.brand = self.brand 157 158 view = EndSessionView() 159 view.request = request 160 view.kwargs = {"application_slug": self.app.slug} 161 view.resolve_provider_application() 162 view.validate() 163 164 self.assertIn("state=test-state-123", view.post_logout_redirect_uri)
Test state parameter is appended to validated redirect URI
166 def test_post_method(self): 167 """Test POST requests work same as GET""" 168 self.client.force_login(self.user) 169 response = self.client.post( 170 reverse( 171 "authentik_providers_oauth2:end-session", 172 kwargs={"application_slug": self.app.slug}, 173 ), 174 { 175 "post_logout_redirect_uri": "http://testserver/logout", 176 "state": "xyz789", 177 "id_token_hint": self._id_token_hint(self.brand.domain), 178 }, 179 HTTP_HOST=self.brand.domain, 180 ) 181 self.assertEqual(response.status_code, 302)
Test POST requests work same as GET
184class TestEndSessionAPI(OAuthTestCase): 185 """Test End Session API functionality""" 186 187 def setUp(self) -> None: 188 super().setUp() 189 self.user = create_test_admin_user() 190 self.client.force_login(self.user) 191 192 def test_post_logout_redirect_uris_create(self): 193 """Test creating provider with post_logout redirect_uris""" 194 response = self.client.post( 195 reverse("authentik_api:oauth2provider-list"), 196 data={ 197 "name": generate_id(), 198 "authorization_flow": create_test_flow().pk, 199 "invalidation_flow": create_test_flow().pk, 200 "redirect_uris": [ 201 { 202 "matching_mode": "strict", 203 "url": "http://testserver/callback", 204 "redirect_uri_type": "authorization", 205 }, 206 { 207 "matching_mode": "strict", 208 "url": "http://testserver/logout", 209 "redirect_uri_type": "logout", 210 }, 211 { 212 "matching_mode": "regex", 213 "url": "https://.*\\.example\\.com/logout", 214 "redirect_uri_type": "logout", 215 }, 216 ], 217 }, 218 content_type="application/json", 219 ) 220 self.assertEqual(response.status_code, 201) 221 provider_data = response.json() 222 post_logout_uris = [ 223 u for u in provider_data["redirect_uris"] if u["redirect_uri_type"] == "logout" 224 ] 225 self.assertEqual(len(post_logout_uris), 2) 226 227 def test_post_logout_redirect_uris_invalid_regex(self): 228 """Test that invalid regex patterns are rejected""" 229 response = self.client.post( 230 reverse("authentik_api:oauth2provider-list"), 231 data={ 232 "name": generate_id(), 233 "authorization_flow": create_test_flow().pk, 234 "invalidation_flow": create_test_flow().pk, 235 "redirect_uris": [ 236 { 237 "matching_mode": "strict", 238 "url": "http://testserver/callback", 239 "redirect_uri_type": "authorization", 240 }, 241 { 242 "matching_mode": "regex", 243 "url": "**invalid**", 244 "redirect_uri_type": "logout", 245 }, 246 ], 247 }, 248 content_type="application/json", 249 ) 250 self.assertEqual(response.status_code, 400) 251 self.assertIn("redirect_uris", response.json()) 252 253 def test_post_logout_redirect_uris_update(self): 254 """Test updating redirect_uris with logout type""" 255 # First create a provider 256 provider = OAuth2Provider.objects.create( 257 name=generate_id(), 258 authorization_flow=create_test_flow(), 259 redirect_uris=[ 260 RedirectURI( 261 RedirectURIMatchingMode.STRICT, 262 "http://testserver/callback", 263 RedirectURIType.AUTHORIZATION, 264 ), 265 ], 266 ) 267 268 # Update with post_logout redirect URIs 269 response = self.client.patch( 270 reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider.pk}), 271 data={ 272 "redirect_uris": [ 273 { 274 "matching_mode": "strict", 275 "url": "http://testserver/callback", 276 "redirect_uri_type": "authorization", 277 }, 278 { 279 "matching_mode": "strict", 280 "url": "http://testserver/logout", 281 "redirect_uri_type": "logout", 282 }, 283 ], 284 }, 285 content_type="application/json", 286 ) 287 self.assertEqual(response.status_code, 200) 288 289 # Verify the update 290 provider.refresh_from_db() 291 self.assertEqual(len(provider.post_logout_redirect_uris), 1) 292 self.assertEqual(provider.post_logout_redirect_uris[0].url, "http://testserver/logout")
Test End Session API functionality
187 def setUp(self) -> None: 188 super().setUp() 189 self.user = create_test_admin_user() 190 self.client.force_login(self.user)
Hook method for setting up the test fixture before exercising it.
192 def test_post_logout_redirect_uris_create(self): 193 """Test creating provider with post_logout redirect_uris""" 194 response = self.client.post( 195 reverse("authentik_api:oauth2provider-list"), 196 data={ 197 "name": generate_id(), 198 "authorization_flow": create_test_flow().pk, 199 "invalidation_flow": create_test_flow().pk, 200 "redirect_uris": [ 201 { 202 "matching_mode": "strict", 203 "url": "http://testserver/callback", 204 "redirect_uri_type": "authorization", 205 }, 206 { 207 "matching_mode": "strict", 208 "url": "http://testserver/logout", 209 "redirect_uri_type": "logout", 210 }, 211 { 212 "matching_mode": "regex", 213 "url": "https://.*\\.example\\.com/logout", 214 "redirect_uri_type": "logout", 215 }, 216 ], 217 }, 218 content_type="application/json", 219 ) 220 self.assertEqual(response.status_code, 201) 221 provider_data = response.json() 222 post_logout_uris = [ 223 u for u in provider_data["redirect_uris"] if u["redirect_uri_type"] == "logout" 224 ] 225 self.assertEqual(len(post_logout_uris), 2)
Test creating provider with post_logout redirect_uris
227 def test_post_logout_redirect_uris_invalid_regex(self): 228 """Test that invalid regex patterns are rejected""" 229 response = self.client.post( 230 reverse("authentik_api:oauth2provider-list"), 231 data={ 232 "name": generate_id(), 233 "authorization_flow": create_test_flow().pk, 234 "invalidation_flow": create_test_flow().pk, 235 "redirect_uris": [ 236 { 237 "matching_mode": "strict", 238 "url": "http://testserver/callback", 239 "redirect_uri_type": "authorization", 240 }, 241 { 242 "matching_mode": "regex", 243 "url": "**invalid**", 244 "redirect_uri_type": "logout", 245 }, 246 ], 247 }, 248 content_type="application/json", 249 ) 250 self.assertEqual(response.status_code, 400) 251 self.assertIn("redirect_uris", response.json())
Test that invalid regex patterns are rejected
253 def test_post_logout_redirect_uris_update(self): 254 """Test updating redirect_uris with logout type""" 255 # First create a provider 256 provider = OAuth2Provider.objects.create( 257 name=generate_id(), 258 authorization_flow=create_test_flow(), 259 redirect_uris=[ 260 RedirectURI( 261 RedirectURIMatchingMode.STRICT, 262 "http://testserver/callback", 263 RedirectURIType.AUTHORIZATION, 264 ), 265 ], 266 ) 267 268 # Update with post_logout redirect URIs 269 response = self.client.patch( 270 reverse("authentik_api:oauth2provider-detail", kwargs={"pk": provider.pk}), 271 data={ 272 "redirect_uris": [ 273 { 274 "matching_mode": "strict", 275 "url": "http://testserver/callback", 276 "redirect_uri_type": "authorization", 277 }, 278 { 279 "matching_mode": "strict", 280 "url": "http://testserver/logout", 281 "redirect_uri_type": "logout", 282 }, 283 ], 284 }, 285 content_type="application/json", 286 ) 287 self.assertEqual(response.status_code, 200) 288 289 # Verify the update 290 provider.refresh_from_db() 291 self.assertEqual(len(provider.post_logout_redirect_uris), 1) 292 self.assertEqual(provider.post_logout_redirect_uris[0].url, "http://testserver/logout")
Test updating redirect_uris with logout type