def test_outpost_validaton(self): """Test Outpost validation""" valid = OutpostSerializer( data={ "name": "foo", "type": OutpostType.PROXY, "config": default_outpost_config(), "providers": [ ProxyProvider.objects.create( name="test", authorization_flow=create_test_flow() ).pk ], } ) self.assertTrue(valid.is_valid()) invalid = OutpostSerializer( data={ "name": "foo", "type": OutpostType.PROXY, "config": default_outpost_config(), "providers": [ LDAPProvider.objects.create( name="test", authorization_flow=create_test_flow() ).pk ], } ) self.assertFalse(invalid.is_valid()) self.assertIn("providers", invalid.errors)
def setUp(self) -> None: super().setUp() ObjectManager().run() self.app = Application.objects.create(name="test", slug="test") self.provider: OAuth2Provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="", signing_key=create_test_cert(), ) self.provider.property_mappings.set(ScopeMapping.objects.all()) # Needs to be assigned to an application for iss to be set self.app.provider = self.provider self.app.save() self.user = create_test_admin_user() self.token: RefreshToken = RefreshToken.objects.create( provider=self.provider, user=self.user, access_token=generate_id(), refresh_token=generate_id(), _scope="openid user profile", _id_token=json.dumps(asdict(IDToken("foo", "bar"), )), )
def test_used_by(self): """Test used_by endpoint""" self.client.force_login(create_test_admin_user()) keypair = create_test_cert() provider = OAuth2Provider.objects.create( name="test", client_id="test", client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://localhost", signing_key=keypair, ) response = self.client.get( reverse( "authentik_api:certificatekeypair-used-by", kwargs={"pk": keypair.pk}, )) self.assertEqual(200, response.status_code) self.assertJSONEqual( response.content.decode(), [{ "app": "authentik_providers_oauth2", "model_name": "oauth2provider", "pk": str(provider.pk), "name": str(provider), "action": DeleteAction.SET_NULL.name, }], )
def test_request_refresh_token(self): """test request param""" provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", signing_key=create_test_cert(), ) header = b64encode(f"{provider.client_id}:{provider.client_secret}". encode()).decode() user = create_test_admin_user() token: RefreshToken = RefreshToken.objects.create( provider=provider, user=user, refresh_token=generate_id(), ) request = self.factory.post( "/", data={ "grant_type": GRANT_TYPE_REFRESH_TOKEN, "refresh_token": token.refresh_token, "redirect_uri": "http://local.invalid", }, HTTP_AUTHORIZATION=f"Basic {header}", ) params = TokenParams.parse(request, provider, provider.client_id, provider.client_secret) self.assertEqual(params.provider, provider)
def test_request_auth_code(self): """test request param""" provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://testserver", signing_key=create_test_cert(), ) header = b64encode(f"{provider.client_id}:{provider.client_secret}". encode()).decode() user = create_test_admin_user() code = AuthorizationCode.objects.create(code="foobar", provider=provider, user=user) request = self.factory.post( "/", data={ "grant_type": GRANT_TYPE_AUTHORIZATION_CODE, "code": code.code, "redirect_uri": "http://testserver", }, HTTP_AUTHORIZATION=f"Basic {header}", ) params = TokenParams.parse(request, provider, provider.client_id, provider.client_secret) self.assertEqual(params.provider, provider) with self.assertRaises(TokenError): TokenParams.parse(request, provider, provider.client_id, generate_key())
def test_invalid_redirect_uri(self): """test missing/invalid redirect URI""" OAuth2Provider.objects.create( name="test", client_id="test", authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", ) with self.assertRaises(RedirectUriError): request = self.factory.get("/", data={ "response_type": "code", "client_id": "test" }) OAuthAuthorizationParams.from_request(request) with self.assertRaises(RedirectUriError): request = self.factory.get( "/", data={ "response_type": "code", "client_id": "test", "redirect_uri": "http://localhost", }, ) OAuthAuthorizationParams.from_request(request)
def test_refresh_token_revoke(self): """test request param""" provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://testserver", signing_key=create_test_cert(), ) # Needs to be assigned to an application for iss to be set self.app.provider = provider self.app.save() header = b64encode(f"{provider.client_id}:{provider.client_secret}". encode()).decode() user = create_test_admin_user() token: RefreshToken = RefreshToken.objects.create( provider=provider, user=user, refresh_token=generate_id(), ) # Create initial refresh token response = self.client.post( reverse("authentik_providers_oauth2:token"), data={ "grant_type": GRANT_TYPE_REFRESH_TOKEN, "refresh_token": token.refresh_token, "redirect_uri": "http://testserver", }, HTTP_AUTHORIZATION=f"Basic {header}", ) new_token: RefreshToken = (RefreshToken.objects.filter( user=user).exclude(pk=token.pk).first()) # Post again with initial token -> get new refresh token # and revoke old one response = self.client.post( reverse("authentik_providers_oauth2:token"), data={ "grant_type": GRANT_TYPE_REFRESH_TOKEN, "refresh_token": new_token.refresh_token, "redirect_uri": "http://local.invalid", }, HTTP_AUTHORIZATION=f"Basic {header}", ) self.assertEqual(response.status_code, 200) # Post again with old token, is now revoked and should error response = self.client.post( reverse("authentik_providers_oauth2:token"), data={ "grant_type": GRANT_TYPE_REFRESH_TOKEN, "refresh_token": new_token.refresh_token, "redirect_uri": "http://local.invalid", }, HTTP_AUTHORIZATION=f"Basic {header}", ) self.assertEqual(response.status_code, 400) self.assertTrue( Event.objects.filter( action=EventAction.SUSPICIOUS_REQUEST).exists())
def setUp(self): ObjectManager().run() cert = create_test_cert() self.provider: SAMLProvider = SAMLProvider.objects.create( authorization_flow=create_test_flow(), acs_url="http://testserver/source/saml/provider/acs/", signing_kp=cert, verification_kp=cert, ) self.provider.property_mappings.set(SAMLPropertyMapping.objects.all()) self.provider.save() self.source = SAMLSource.objects.create( slug="provider", issuer="authentik", signing_kp=cert, pre_authentication_flow=create_test_flow(), ) self.factory = RequestFactory()
def test_default_view(self): """Test that ToDefaultFlow returns the expected URL""" Flow.objects.filter(designation=FlowDesignation.INVALIDATION).delete() flow = create_test_flow(FlowDesignation.INVALIDATION) response = self.client.get( reverse("authentik_flows:default-invalidation"), ) expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, expected_url)
def test_metadata_without_signautre(self): """Test Metadata generation being valid""" source = SAMLSource.objects.create( slug="provider", issuer="authentik", pre_authentication_flow=create_test_flow(), ) request = self.factory.get("/") xml = MetadataProcessor(source, request).build_entity_descriptor() metadata = ElementTree.fromstring(xml) self.assertEqual(metadata.attrib["entityID"], "authentik")
def test_recovery(self): """Test user recovery link (no recovery flow set)""" flow = create_test_flow(FlowDesignation.RECOVERY) tenant: Tenant = create_test_tenant() tenant.flow_recovery = flow tenant.save() self.client.force_login(self.admin) response = self.client.get( reverse("authentik_api:user-recovery", kwargs={"pk": self.user.pk}) ) self.assertEqual(response.status_code, 200)
def test_metadata(self): """Test metadata export (normal)""" self.client.logout() provider = SAMLProvider.objects.create( name="test", authorization_flow=create_test_flow(), ) Application.objects.create(name="test", provider=provider, slug="test") response = self.client.get( reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}), ) self.assertEqual(200, response.status_code)
def test_kubernetes_controller_ingress(self): """Test Kubernetes Controller's Ingress""" provider: ProxyProvider = ProxyProvider.objects.create( name="test", internal_host="http://localhost", external_host="https://localhost", authorization_flow=create_test_flow(), ) provider2: ProxyProvider = ProxyProvider.objects.create( name="test2", internal_host="http://otherhost", external_host="https://otherhost", mode=ProxyMode.FORWARD_SINGLE, authorization_flow=create_test_flow(), ) service_connection = KubernetesServiceConnection.objects.first() outpost: Outpost = Outpost.objects.create( name="test", type=OutpostType.PROXY, service_connection=service_connection, ) outpost.providers.add(provider) self.controller = ProxyKubernetesController(outpost, service_connection) ingress_rec = IngressReconciler(self.controller) ingress = ingress_rec.retrieve() self.assertEqual(len(ingress.spec.rules), 1) self.assertEqual(ingress.spec.rules[0].host, "localhost") # add provider, check again outpost.providers.add(provider2) ingress = ingress_rec.retrieve() self.assertEqual(len(ingress.spec.rules), 2) self.assertEqual(ingress.spec.rules[0].host, "localhost") self.assertEqual(ingress.spec.rules[1].host, "otherhost")
def test_recovery_email_no_stage(self): """Test user recovery link (no email stage)""" self.user.email = "*****@*****.**" self.user.save() flow = create_test_flow(designation=FlowDesignation.RECOVERY) tenant: Tenant = create_test_tenant() tenant.flow_recovery = flow tenant.save() self.client.force_login(self.admin) response = self.client.get( reverse("authentik_api:user-recovery-email", kwargs={"pk": self.user.pk}) ) self.assertEqual(response.status_code, 404)
def test_hs256(self): """Test JWKS request with HS256""" provider = OAuth2Provider.objects.create( name="test", client_id="test", authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", ) app = Application.objects.create(name="test", slug="test", provider=provider) response = self.client.get( reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})) self.assertJSONEqual(response.content.decode(), {})
def test_metadata_schema(self): """Test Metadata generation being valid""" source = SAMLSource.objects.create( slug="provider", issuer="authentik", signing_kp=create_test_cert(), pre_authentication_flow=create_test_flow(), ) request = self.factory.get("/") xml = MetadataProcessor(source, request).build_entity_descriptor() metadata = etree.fromstring(xml) # nosec schema = etree.XMLSchema( etree.parse("xml/saml-schema-metadata-2.0.xsd")) # nosec self.assertTrue(schema.validate(metadata))
def test_import_failed(self): """Test metadata import (invalid xml)""" with TemporaryFile() as metadata: metadata.write(b"invalid") metadata.seek(0) response = self.client.post( reverse("authentik_api:samlprovider-import-metadata"), { "file": metadata, "name": "test", "authorization_flow": create_test_flow().slug, }, format="multipart", ) self.assertEqual(400, response.status_code)
def test_outpost_config(self): """Test Outpost's config field""" provider = ProxyProvider.objects.create(name="test", authorization_flow=create_test_flow()) invalid = OutpostSerializer(data={"name": "foo", "providers": [provider.pk], "config": ""}) self.assertFalse(invalid.is_valid()) self.assertIn("config", invalid.errors) valid = OutpostSerializer( data={ "name": "foo", "providers": [provider.pk], "config": default_outpost_config("foo"), "type": OutpostType.PROXY, } ) self.assertTrue(valid.is_valid())
def test_default_view_invalid_plan(self): """Test that ToDefaultFlow returns the expected URL (with an invalid plan)""" Flow.objects.filter(designation=FlowDesignation.INVALIDATION).delete() flow = create_test_flow(FlowDesignation.INVALIDATION) plan = FlowPlan(flow_pk=flow.pk.hex + "aa") session = self.client.session session[SESSION_KEY_PLAN] = plan session.save() response = self.client.get( reverse("authentik_flows:default-invalidation"), ) expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, expected_url)
def test_metadata_invalid(self): """Test metadata export (invalid)""" self.client.logout() # Provider without application provider = SAMLProvider.objects.create( name="test", authorization_flow=create_test_flow(), ) response = self.client.get( reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}), ) self.assertEqual(200, response.status_code) response = self.client.get( reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}), ) self.assertEqual(404, response.status_code)
def test_signed_static(self): """Test post request with static request""" provider = SAMLProvider( name="aws", authorization_flow=create_test_flow(), acs_url=("https://eu-central-1.signin.aws.amazon.com/platform/" "saml/acs/2d737f96-55fb-4035-953e-5e24134eb778"), audience="https://10.120.20.200/saml-sp/SAML2/POST", issuer="https://10.120.20.200/saml-sp/SAML2/POST", signing_kp=create_test_cert(), ) parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST) self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re") self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
def test_refresh_token_view(self): """test request param""" provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", signing_key=create_test_cert(), ) # Needs to be assigned to an application for iss to be set self.app.provider = provider self.app.save() header = b64encode(f"{provider.client_id}:{provider.client_secret}". encode()).decode() user = create_test_admin_user() token: RefreshToken = RefreshToken.objects.create( provider=provider, user=user, refresh_token=generate_id(), ) response = self.client.post( reverse("authentik_providers_oauth2:token"), data={ "grant_type": GRANT_TYPE_REFRESH_TOKEN, "refresh_token": token.refresh_token, "redirect_uri": "http://local.invalid", }, HTTP_AUTHORIZATION=f"Basic {header}", HTTP_ORIGIN="http://local.invalid", ) new_token: RefreshToken = (RefreshToken.objects.filter( user=user).exclude(pk=token.pk).first()) self.assertEqual(response["Access-Control-Allow-Credentials"], "true") self.assertEqual(response["Access-Control-Allow-Origin"], "http://local.invalid") self.assertJSONEqual( response.content.decode(), { "access_token": new_token.access_token, "refresh_token": new_token.refresh_token, "token_type": "bearer", "expires_in": 2592000, "id_token": provider.encode(new_token.id_token.to_dict(), ), }, ) self.validate_jwt(new_token, provider)
def test_rs256(self): """Test JWKS request with RS256""" provider = OAuth2Provider.objects.create( name="test", client_id="test", authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", signing_key=create_test_cert(), ) app = Application.objects.create(name="test", slug="test", provider=provider) response = self.client.get( reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})) body = json.loads(response.content.decode()) self.assertEqual(len(body["keys"]), 1)
def test_import_success(self): """Test metadata import (success case)""" with TemporaryFile() as metadata: metadata.write(METADATA_SIMPLE.encode()) metadata.seek(0) response = self.client.post( reverse("authentik_api:samlprovider-import-metadata"), { "file": metadata, "name": "test", "authorization_flow": create_test_flow(FlowDesignation.AUTHORIZATION).slug, }, format="multipart", ) self.assertEqual(204, response.status_code)
def setUp(self): super().setUp() # Ensure that local connection have been created outpost_local_connection() self.provider: ProxyProvider = ProxyProvider.objects.create( name="test", internal_host="http://localhost", external_host="http://localhost", authorization_flow=create_test_flow(), ) self.service_connection = KubernetesServiceConnection.objects.first() self.outpost: Outpost = Outpost.objects.create( name="test", type=OutpostType.PROXY, service_connection=self.service_connection, ) self.outpost.providers.add(self.provider) self.outpost.save()
def test_service_account_permissions(self): """Test that the service account has correct permissions""" provider: ProxyProvider = ProxyProvider.objects.create( name="test", internal_host="http://localhost", external_host="http://localhost", authorization_flow=create_test_flow(), ) outpost: Outpost = Outpost.objects.create( name="test", type=OutpostType.PROXY, ) # Before we add a provider, the user should only have access to the outpost permissions = UserObjectPermission.objects.filter(user=outpost.user) self.assertEqual(len(permissions), 1) self.assertEqual(permissions[0].object_pk, str(outpost.pk)) # We add a provider, user should only have access to outpost and provider outpost.providers.add(provider) permissions = UserObjectPermission.objects.filter(user=outpost.user).order_by( "content_type__model" ) self.assertEqual(len(permissions), 2) self.assertEqual(permissions[0].object_pk, str(outpost.pk)) self.assertEqual(permissions[1].object_pk, str(provider.pk)) # Provider requires a certificate-key-pair, user should have permissions for it keypair = create_test_cert() provider.certificate = keypair provider.save() permissions = UserObjectPermission.objects.filter(user=outpost.user).order_by( "content_type__model" ) self.assertEqual(len(permissions), 3) self.assertEqual(permissions[0].object_pk, str(keypair.pk)) self.assertEqual(permissions[1].object_pk, str(outpost.pk)) self.assertEqual(permissions[2].object_pk, str(provider.pk)) # Remove provider from outpost, user should only have access to outpost outpost.providers.remove(provider) permissions = UserObjectPermission.objects.filter(user=outpost.user) self.assertEqual(len(permissions), 1) self.assertEqual(permissions[0].object_pk, str(outpost.pk))
def test_auth_code_view(self): """test request param""" provider = OAuth2Provider.objects.create( name="test", client_id=generate_id(), client_secret=generate_key(), authorization_flow=create_test_flow(), redirect_uris="http://local.invalid", signing_key=create_test_cert(), ) # Needs to be assigned to an application for iss to be set self.app.provider = provider self.app.save() header = b64encode(f"{provider.client_id}:{provider.client_secret}". encode()).decode() user = create_test_admin_user() code = AuthorizationCode.objects.create(code="foobar", provider=provider, user=user, is_open_id=True) response = self.client.post( reverse("authentik_providers_oauth2:token"), data={ "grant_type": GRANT_TYPE_AUTHORIZATION_CODE, "code": code.code, "redirect_uri": "http://local.invalid", }, HTTP_AUTHORIZATION=f"Basic {header}", ) new_token: RefreshToken = RefreshToken.objects.filter( user=user).first() self.assertJSONEqual( response.content.decode(), { "access_token": new_token.access_token, "refresh_token": new_token.refresh_token, "token_type": "bearer", "expires_in": 2592000, "id_token": provider.encode(new_token.id_token.to_dict(), ), }, ) self.validate_jwt(new_token, provider)
def test_recovery_email(self): """Test user recovery link""" self.user.email = "*****@*****.**" self.user.save() flow = create_test_flow(FlowDesignation.RECOVERY) tenant: Tenant = create_test_tenant() tenant.flow_recovery = flow tenant.save() stage = EmailStage.objects.create(name="email") self.client.force_login(self.admin) response = self.client.get( reverse( "authentik_api:user-recovery-email", kwargs={"pk": self.user.pk}, ) + f"?email_stage={stage.pk}" ) self.assertEqual(response.status_code, 204)
def test_signed_detached_static(self): """Test request with detached signature, taken from https://www.samltool.com/generic_sso_req.php""" static_keypair = CertificateKeyPair.objects.create( name="samltool", certificate_data=REDIRECT_CERT) provider = SAMLProvider( name="samltool", authorization_flow=create_test_flow(), acs_url="https://10.120.20.200/saml-sp/SAML2/POST", audience="https://10.120.20.200/saml-sp/SAML2/POST", issuer="https://10.120.20.200/saml-sp/SAML2/POST", signing_kp=static_keypair, verification_kp=static_keypair, ) parsed_request = AuthNRequestParser(provider).parse_detached( REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG) self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab") self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED) self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)
def test_kubernetes_controller_static(self): """Test Kubernetes Controller""" provider: ProxyProvider = ProxyProvider.objects.create( name="test", internal_host="http://localhost", external_host="http://localhost", authorization_flow=create_test_flow(), ) service_connection = KubernetesServiceConnection.objects.first() outpost: Outpost = Outpost.objects.create( name="test", type=OutpostType.PROXY, service_connection=service_connection, ) outpost.providers.add(provider) outpost.save() self.controller = ProxyKubernetesController(outpost, service_connection) manifest = self.controller.get_static_deployment() self.assertEqual( len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)