tests/e2e: fix tests to work without docker network_mode host (#8035)
* tests/e2e: start fixing tests to work without docker network_mode host Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate saml and oauth source Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update deps (mainly to update lxml which was causing a segfault on macos) Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate saml source Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix sentry env in testing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make oauth types name and slug make more sense Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate ldap Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make tests run with --keepdb? partially? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate radius Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix proxy provider first half Signed-off-by: Jens Langhammer <jens@goauthentik.io> * install libxml2-dev to work around seg fault? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * actually that doesn't change anything since use latest libxml2 Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * refactor did not refactor the code Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		| @ -40,10 +40,9 @@ class PytestTestRunner(DiscoverRunner):  # pragma: no cover | |||||||
|             f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}", |             f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}", | ||||||
|         ) |         ) | ||||||
|         CONFIG.set("error_reporting.sample_rate", 0) |         CONFIG.set("error_reporting.sample_rate", 0) | ||||||
|         sentry_init( |         CONFIG.set("error_reporting.environment", "testing") | ||||||
|             environment="testing", |         CONFIG.set("error_reporting.send_pii", True) | ||||||
|             send_default_pii=True, |         sentry_init() | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def add_arguments(cls, parser: ArgumentParser): |     def add_arguments(cls, parser: ArgumentParser): | ||||||
|  | |||||||
| @ -99,7 +99,9 @@ class OAuthSourceSerializer(SourceSerializer): | |||||||
|         ]: |         ]: | ||||||
|             if getattr(provider_type, url, None) is None: |             if getattr(provider_type, url, None) is None: | ||||||
|                 if url not in attrs: |                 if url not in attrs: | ||||||
|                     raise ValidationError(f"{url} is required for provider {provider_type.name}") |                     raise ValidationError( | ||||||
|  |                         f"{url} is required for provider {provider_type.verbose_name}" | ||||||
|  |                     ) | ||||||
|         return attrs |         return attrs | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  | |||||||
| @ -104,8 +104,8 @@ class AppleType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = AppleOAuth2Callback |     callback_view = AppleOAuth2Callback | ||||||
|     redirect_view = AppleOAuthRedirect |     redirect_view = AppleOAuthRedirect | ||||||
|     name = "Apple" |     verbose_name = "Apple" | ||||||
|     slug = "apple" |     name = "apple" | ||||||
|  |  | ||||||
|     authorization_url = "https://appleid.apple.com/auth/authorize" |     authorization_url = "https://appleid.apple.com/auth/authorize" | ||||||
|     access_token_url = "https://appleid.apple.com/auth/token"  # nosec |     access_token_url = "https://appleid.apple.com/auth/token"  # nosec | ||||||
|  | |||||||
| @ -43,8 +43,8 @@ class AzureADType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = AzureADOAuthCallback |     callback_view = AzureADOAuthCallback | ||||||
|     redirect_view = AzureADOAuthRedirect |     redirect_view = AzureADOAuthRedirect | ||||||
|     name = "Azure AD" |     verbose_name = "Azure AD" | ||||||
|     slug = "azuread" |     name = "azuread" | ||||||
|  |  | ||||||
|     urls_customizable = True |     urls_customizable = True | ||||||
|  |  | ||||||
|  | |||||||
| @ -36,8 +36,8 @@ class DiscordType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = DiscordOAuth2Callback |     callback_view = DiscordOAuth2Callback | ||||||
|     redirect_view = DiscordOAuthRedirect |     redirect_view = DiscordOAuthRedirect | ||||||
|     name = "Discord" |     verbose_name = "Discord" | ||||||
|     slug = "discord" |     name = "discord" | ||||||
|  |  | ||||||
|     authorization_url = "https://discord.com/api/oauth2/authorize" |     authorization_url = "https://discord.com/api/oauth2/authorize" | ||||||
|     access_token_url = "https://discord.com/api/oauth2/token"  # nosec |     access_token_url = "https://discord.com/api/oauth2/token"  # nosec | ||||||
|  | |||||||
| @ -48,8 +48,8 @@ class FacebookType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = FacebookOAuth2Callback |     callback_view = FacebookOAuth2Callback | ||||||
|     redirect_view = FacebookOAuthRedirect |     redirect_view = FacebookOAuthRedirect | ||||||
|     name = "Facebook" |     verbose_name = "Facebook" | ||||||
|     slug = "facebook" |     name = "facebook" | ||||||
|  |  | ||||||
|     authorization_url = "https://www.facebook.com/v7.0/dialog/oauth" |     authorization_url = "https://www.facebook.com/v7.0/dialog/oauth" | ||||||
|     access_token_url = "https://graph.facebook.com/v7.0/oauth/access_token"  # nosec |     access_token_url = "https://graph.facebook.com/v7.0/oauth/access_token"  # nosec | ||||||
|  | |||||||
| @ -68,8 +68,8 @@ class GitHubType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = GitHubOAuth2Callback |     callback_view = GitHubOAuth2Callback | ||||||
|     redirect_view = GitHubOAuthRedirect |     redirect_view = GitHubOAuthRedirect | ||||||
|     name = "GitHub" |     verbose_name = "GitHub" | ||||||
|     slug = "github" |     name = "github" | ||||||
|  |  | ||||||
|     urls_customizable = True |     urls_customizable = True | ||||||
|  |  | ||||||
|  | |||||||
| @ -34,8 +34,8 @@ class GoogleType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = GoogleOAuth2Callback |     callback_view = GoogleOAuth2Callback | ||||||
|     redirect_view = GoogleOAuthRedirect |     redirect_view = GoogleOAuthRedirect | ||||||
|     name = "Google" |     verbose_name = "Google" | ||||||
|     slug = "google" |     name = "google" | ||||||
|  |  | ||||||
|     authorization_url = "https://accounts.google.com/o/oauth2/auth" |     authorization_url = "https://accounts.google.com/o/oauth2/auth" | ||||||
|     access_token_url = "https://oauth2.googleapis.com/token"  # nosec |     access_token_url = "https://oauth2.googleapis.com/token"  # nosec | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ class MailcowType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = MailcowOAuth2Callback |     callback_view = MailcowOAuth2Callback | ||||||
|     redirect_view = MailcowOAuthRedirect |     redirect_view = MailcowOAuthRedirect | ||||||
|     name = "Mailcow" |     verbose_name = "Mailcow" | ||||||
|     slug = "mailcow" |     name = "mailcow" | ||||||
|  |  | ||||||
|     urls_customizable = True |     urls_customizable = True | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ class OpenIDConnectType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = OpenIDConnectOAuth2Callback |     callback_view = OpenIDConnectOAuth2Callback | ||||||
|     redirect_view = OpenIDConnectOAuthRedirect |     redirect_view = OpenIDConnectOAuthRedirect | ||||||
|     name = "OpenID Connect" |     verbose_name = "OpenID Connect" | ||||||
|     slug = "openidconnect" |     name = "openidconnect" | ||||||
|  |  | ||||||
|     urls_customizable = True |     urls_customizable = True | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ class OktaType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = OktaOAuth2Callback |     callback_view = OktaOAuth2Callback | ||||||
|     redirect_view = OktaOAuthRedirect |     redirect_view = OktaOAuthRedirect | ||||||
|     name = "Okta" |     verbose_name = "Okta" | ||||||
|     slug = "okta" |     name = "okta" | ||||||
|  |  | ||||||
|     urls_customizable = True |     urls_customizable = True | ||||||
|  | |||||||
| @ -43,8 +43,8 @@ class PatreonType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = PatreonOAuthCallback |     callback_view = PatreonOAuthCallback | ||||||
|     redirect_view = PatreonOAuthRedirect |     redirect_view = PatreonOAuthRedirect | ||||||
|     name = "Patreon" |     verbose_name = "Patreon" | ||||||
|     slug = "patreon" |     name = "patreon" | ||||||
|  |  | ||||||
|     authorization_url = "https://www.patreon.com/oauth2/authorize" |     authorization_url = "https://www.patreon.com/oauth2/authorize" | ||||||
|     access_token_url = "https://www.patreon.com/api/oauth2/token"  # nosec |     access_token_url = "https://www.patreon.com/api/oauth2/token"  # nosec | ||||||
|  | |||||||
| @ -51,8 +51,8 @@ class RedditType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = RedditOAuth2Callback |     callback_view = RedditOAuth2Callback | ||||||
|     redirect_view = RedditOAuthRedirect |     redirect_view = RedditOAuthRedirect | ||||||
|     name = "Reddit" |     verbose_name = "Reddit" | ||||||
|     slug = "reddit" |     name = "reddit" | ||||||
|  |  | ||||||
|     authorization_url = "https://www.reddit.com/api/v1/authorize" |     authorization_url = "https://www.reddit.com/api/v1/authorize" | ||||||
|     access_token_url = "https://www.reddit.com/api/v1/access_token"  # nosec |     access_token_url = "https://www.reddit.com/api/v1/access_token"  # nosec | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ class SourceType: | |||||||
|     callback_view = OAuthCallback |     callback_view = OAuthCallback | ||||||
|     redirect_view = OAuthRedirect |     redirect_view = OAuthRedirect | ||||||
|     name: str = "default" |     name: str = "default" | ||||||
|     slug: str = "default" |     verbose_name: str = "Default source type" | ||||||
|  |  | ||||||
|     urls_customizable = False |     urls_customizable = False | ||||||
|  |  | ||||||
| @ -41,7 +41,7 @@ class SourceType: | |||||||
|  |  | ||||||
|     def icon_url(self) -> str: |     def icon_url(self) -> str: | ||||||
|         """Get Icon URL for login""" |         """Get Icon URL for login""" | ||||||
|         return static(f"authentik/sources/{self.slug}.svg") |         return static(f"authentik/sources/{self.name}.svg") | ||||||
|  |  | ||||||
|     def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: |     def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: | ||||||
|         """Allow types to return custom challenges""" |         """Allow types to return custom challenges""" | ||||||
| @ -77,20 +77,20 @@ class SourceTypeRegistry: | |||||||
|  |  | ||||||
|     def get_name_tuple(self): |     def get_name_tuple(self): | ||||||
|         """Get list of tuples of all registered names""" |         """Get list of tuples of all registered names""" | ||||||
|         return [(x.slug, x.name) for x in self.__sources] |         return [(x.name, x.verbose_name) for x in self.__sources] | ||||||
|  |  | ||||||
|     def find_type(self, type_name: str) -> Type[SourceType]: |     def find_type(self, type_name: str) -> Type[SourceType]: | ||||||
|         """Find type based on source""" |         """Find type based on source""" | ||||||
|         found_type = None |         found_type = None | ||||||
|         for src_type in self.__sources: |         for src_type in self.__sources: | ||||||
|             if src_type.slug == type_name: |             if src_type.name == type_name: | ||||||
|                 return src_type |                 return src_type | ||||||
|         if not found_type: |         if not found_type: | ||||||
|             found_type = SourceType |             found_type = SourceType | ||||||
|             LOGGER.warning( |             LOGGER.warning( | ||||||
|                 "no matching type found, using default", |                 "no matching type found, using default", | ||||||
|                 wanted=type_name, |                 wanted=type_name, | ||||||
|                 have=[x.slug for x in self.__sources], |                 have=[x.name for x in self.__sources], | ||||||
|             ) |             ) | ||||||
|         return found_type |         return found_type | ||||||
|  |  | ||||||
|  | |||||||
| @ -49,8 +49,8 @@ class TwitchType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = TwitchOAuth2Callback |     callback_view = TwitchOAuth2Callback | ||||||
|     redirect_view = TwitchOAuthRedirect |     redirect_view = TwitchOAuthRedirect | ||||||
|     name = "Twitch" |     verbose_name = "Twitch" | ||||||
|     slug = "twitch" |     name = "twitch" | ||||||
|  |  | ||||||
|     authorization_url = "https://id.twitch.tv/oauth2/authorize" |     authorization_url = "https://id.twitch.tv/oauth2/authorize" | ||||||
|     access_token_url = "https://id.twitch.tv/oauth2/token"  # nosec |     access_token_url = "https://id.twitch.tv/oauth2/token"  # nosec | ||||||
|  | |||||||
| @ -66,8 +66,8 @@ class TwitterType(SourceType): | |||||||
|  |  | ||||||
|     callback_view = TwitterOAuthCallback |     callback_view = TwitterOAuthCallback | ||||||
|     redirect_view = TwitterOAuthRedirect |     redirect_view = TwitterOAuthRedirect | ||||||
|     name = "Twitter" |     verbose_name = "Twitter" | ||||||
|     slug = "twitter" |     name = "twitter" | ||||||
|  |  | ||||||
|     authorization_url = "https://twitter.com/i/oauth2/authorize" |     authorization_url = "https://twitter.com/i/oauth2/authorize" | ||||||
|     access_token_url = "https://api.twitter.com/2/oauth2/token"  # nosec |     access_token_url = "https://api.twitter.com/2/oauth2/token"  # nosec | ||||||
|  | |||||||
							
								
								
									
										1065
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1065
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -145,7 +145,12 @@ geoip2 = "*" | |||||||
| gunicorn = "*" | gunicorn = "*" | ||||||
| kubernetes = "*" | kubernetes = "*" | ||||||
| ldap3 = "*" | ldap3 = "*" | ||||||
| lxml = "*" | lxml = [ | ||||||
|  |     # 5.0.0 works with libxml2 2.11.x, which is standard on brew | ||||||
|  |     { version = "5.0.0", platform = "darwin" }, | ||||||
|  |     # 4.9.x works with previous libxml2 versions, which is what we get on linux | ||||||
|  |     { version = "4.9.4", platform = "linux" }, | ||||||
|  | ] | ||||||
| opencontainers = { extras = ["reggie"], version = "*" } | opencontainers = { extras = ["reggie"], version = "*" } | ||||||
| packaging = "*" | packaging = "*" | ||||||
| paramiko = "*" | paramiko = "*" | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """LDAP and Outpost e2e tests""" | """LDAP and Outpost e2e tests""" | ||||||
| from dataclasses import asdict | from dataclasses import asdict | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker.client import DockerClient, from_env | from docker.client import DockerClient, from_env | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| @ -14,13 +12,13 @@ from authentik.blueprints.tests import apply_blueprint, reconcile_app | |||||||
| from authentik.core.models import Application, User | from authentik.core.models import Application, User | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| from authentik.flows.models import Flow | from authentik.flows.models import Flow | ||||||
|  | from authentik.lib.generators import generate_id | ||||||
| from authentik.outposts.apps import MANAGED_OUTPOST | from authentik.outposts.apps import MANAGED_OUTPOST | ||||||
| from authentik.outposts.models import Outpost, OutpostConfig, OutpostType | from authentik.outposts.models import Outpost, OutpostConfig, OutpostType | ||||||
| from authentik.providers.ldap.models import APIAccessMode, LDAPProvider | from authentik.providers.ldap.models import APIAccessMode, LDAPProvider | ||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderLDAP(SeleniumTestCase): | class TestProviderLDAP(SeleniumTestCase): | ||||||
|     """LDAP and Outpost e2e tests""" |     """LDAP and Outpost e2e tests""" | ||||||
|  |  | ||||||
| @ -37,7 +35,10 @@ class TestProviderLDAP(SeleniumTestCase): | |||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"), |             image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"), | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={ | ||||||
|  |                 "3389": "3389", | ||||||
|  |                 "6636": "6636", | ||||||
|  |             }, | ||||||
|             environment={ |             environment={ | ||||||
|                 "AUTHENTIK_HOST": self.live_server_url, |                 "AUTHENTIK_HOST": self.live_server_url, | ||||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, |                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||||
| @ -51,15 +52,15 @@ class TestProviderLDAP(SeleniumTestCase): | |||||||
|         self.user.save() |         self.user.save() | ||||||
|  |  | ||||||
|         ldap: LDAPProvider = LDAPProvider.objects.create( |         ldap: LDAPProvider = LDAPProvider.objects.create( | ||||||
|             name="ldap_provider", |             name=generate_id(), | ||||||
|             authorization_flow=Flow.objects.get(slug="default-authentication-flow"), |             authorization_flow=Flow.objects.get(slug="default-authentication-flow"), | ||||||
|             search_group=self.user.ak_groups.first(), |             search_group=self.user.ak_groups.first(), | ||||||
|             search_mode=APIAccessMode.CACHED, |             search_mode=APIAccessMode.CACHED, | ||||||
|         ) |         ) | ||||||
|         # we need to create an application to actually access the ldap |         # we need to create an application to actually access the ldap | ||||||
|         Application.objects.create(name="ldap", slug="ldap", provider=ldap) |         Application.objects.create(name=generate_id(), slug=generate_id(), provider=ldap) | ||||||
|         outpost: Outpost = Outpost.objects.create( |         outpost: Outpost = Outpost.objects.create( | ||||||
|             name="ldap_outpost", |             name=generate_id(), | ||||||
|             type=OutpostType.LDAP, |             type=OutpostType.LDAP, | ||||||
|             _config=asdict(OutpostConfig(log_level="debug")), |             _config=asdict(OutpostConfig(log_level="debug")), | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """test OAuth Provider flow""" | """test OAuth Provider flow""" | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from typing import Any, Optional | from typing import Any, Optional | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker.types import Healthcheck | from docker.types import Healthcheck | ||||||
| from selenium.webdriver.common.by import By | from selenium.webdriver.common.by import By | ||||||
| @ -18,7 +16,6 @@ from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderOAuth2Github(SeleniumTestCase): | class TestProviderOAuth2Github(SeleniumTestCase): | ||||||
|     """test OAuth Provider flow""" |     """test OAuth Provider flow""" | ||||||
|  |  | ||||||
| @ -32,7 +29,9 @@ class TestProviderOAuth2Github(SeleniumTestCase): | |||||||
|         return { |         return { | ||||||
|             "image": "grafana/grafana:7.1.0", |             "image": "grafana/grafana:7.1.0", | ||||||
|             "detach": True, |             "detach": True, | ||||||
|             "network_mode": "host", |             "ports": { | ||||||
|  |                 "3000": "3000", | ||||||
|  |             }, | ||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], |                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """test OAuth2 OpenID Provider flow""" | """test OAuth2 OpenID Provider flow""" | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from typing import Any, Optional | from typing import Any, Optional | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker.types import Healthcheck | from docker.types import Healthcheck | ||||||
| from selenium.webdriver.common.by import By | from selenium.webdriver.common.by import By | ||||||
| @ -24,7 +22,6 @@ from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, Scope | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderOAuth2OAuth(SeleniumTestCase): | class TestProviderOAuth2OAuth(SeleniumTestCase): | ||||||
|     """test OAuth with OAuth Provider flow""" |     """test OAuth with OAuth Provider flow""" | ||||||
|  |  | ||||||
| @ -38,13 +35,15 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): | |||||||
|         return { |         return { | ||||||
|             "image": "grafana/grafana:7.1.0", |             "image": "grafana/grafana:7.1.0", | ||||||
|             "detach": True, |             "detach": True, | ||||||
|             "network_mode": "host", |  | ||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], |                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||||
|                 interval=5 * 1_000 * 1_000_000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=1 * 1_000 * 1_000_000, |                 start_period=1 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|  |             "ports": { | ||||||
|  |                 "3000": "3000", | ||||||
|  |             }, | ||||||
|             "environment": { |             "environment": { | ||||||
|                 "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", |                 "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", | ||||||
|                 "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id, |                 "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id, | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """test OAuth2 OpenID Provider flow""" | """test OAuth2 OpenID Provider flow""" | ||||||
| from json import loads | from json import loads | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker import DockerClient, from_env | from docker import DockerClient, from_env | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| @ -25,7 +23,6 @@ from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, Scope | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderOAuth2OIDC(SeleniumTestCase): | class TestProviderOAuth2OIDC(SeleniumTestCase): | ||||||
|     """test OAuth with OpenID Provider flow""" |     """test OAuth with OpenID Provider flow""" | ||||||
|  |  | ||||||
| @ -36,13 +33,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): | |||||||
|         super().setUp() |         super().setUp() | ||||||
|  |  | ||||||
|     def setup_client(self) -> Container: |     def setup_client(self) -> Container: | ||||||
|         """Setup client saml-sp container which we test SAML against""" |         """Setup client oidc-test-client container which we test OIDC against""" | ||||||
|         sleep(1) |         sleep(1) | ||||||
|         client: DockerClient = from_env() |         client: DockerClient = from_env() | ||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image="ghcr.io/beryju/oidc-test-client:1.3", |             image="ghcr.io/beryju/oidc-test-client:1.3", | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={ | ||||||
|  |                 "9009": "9009", | ||||||
|  |             }, | ||||||
|             environment={ |             environment={ | ||||||
|                 "OIDC_CLIENT_ID": self.client_id, |                 "OIDC_CLIENT_ID": self.client_id, | ||||||
|                 "OIDC_CLIENT_SECRET": self.client_secret, |                 "OIDC_CLIENT_SECRET": self.client_secret, | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """test OAuth2 OpenID Provider flow""" | """test OAuth2 OpenID Provider flow""" | ||||||
| from json import loads | from json import loads | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker import DockerClient, from_env | from docker import DockerClient, from_env | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| @ -25,7 +23,6 @@ from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, Scope | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | ||||||
|     """test OAuth with OpenID Provider flow""" |     """test OAuth with OpenID Provider flow""" | ||||||
|  |  | ||||||
| @ -36,13 +33,15 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): | |||||||
|         super().setUp() |         super().setUp() | ||||||
|  |  | ||||||
|     def setup_client(self) -> Container: |     def setup_client(self) -> Container: | ||||||
|         """Setup client saml-sp container which we test SAML against""" |         """Setup client oidc-test-client container which we test OIDC against""" | ||||||
|         sleep(1) |         sleep(1) | ||||||
|         client: DockerClient = from_env() |         client: DockerClient = from_env() | ||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image="ghcr.io/beryju/oidc-test-client:1.3", |             image="ghcr.io/beryju/oidc-test-client:1.3", | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={ | ||||||
|  |                 "9009": "9009", | ||||||
|  |             }, | ||||||
|             environment={ |             environment={ | ||||||
|                 "OIDC_CLIENT_ID": self.client_id, |                 "OIDC_CLIENT_ID": self.client_id, | ||||||
|                 "OIDC_CLIENT_SECRET": self.client_secret, |                 "OIDC_CLIENT_SECRET": self.client_secret, | ||||||
|  | |||||||
| @ -21,7 +21,6 @@ from authentik.providers.proxy.models import ProxyProvider | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderProxy(SeleniumTestCase): | class TestProviderProxy(SeleniumTestCase): | ||||||
|     """Proxy and Outpost e2e tests""" |     """Proxy and Outpost e2e tests""" | ||||||
|  |  | ||||||
| @ -36,7 +35,9 @@ class TestProviderProxy(SeleniumTestCase): | |||||||
|         return { |         return { | ||||||
|             "image": "traefik/whoami:latest", |             "image": "traefik/whoami:latest", | ||||||
|             "detach": True, |             "detach": True, | ||||||
|             "network_mode": "host", |             "ports": { | ||||||
|  |                 "80": "80", | ||||||
|  |             }, | ||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -46,7 +47,9 @@ class TestProviderProxy(SeleniumTestCase): | |||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"), |             image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"), | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={ | ||||||
|  |                 "9000": "9000", | ||||||
|  |             }, | ||||||
|             environment={ |             environment={ | ||||||
|                 "AUTHENTIK_HOST": self.live_server_url, |                 "AUTHENTIK_HOST": self.live_server_url, | ||||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, |                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||||
| @ -78,7 +81,7 @@ class TestProviderProxy(SeleniumTestCase): | |||||||
|             authorization_flow=Flow.objects.get( |             authorization_flow=Flow.objects.get( | ||||||
|                 slug="default-provider-authorization-implicit-consent" |                 slug="default-provider-authorization-implicit-consent" | ||||||
|             ), |             ), | ||||||
|             internal_host="http://localhost", |             internal_host=f"http://{self.host}", | ||||||
|             external_host="http://localhost:9000", |             external_host="http://localhost:9000", | ||||||
|         ) |         ) | ||||||
|         # Ensure OAuth2 Params are set |         # Ensure OAuth2 Params are set | ||||||
| @ -145,7 +148,7 @@ class TestProviderProxy(SeleniumTestCase): | |||||||
|             authorization_flow=Flow.objects.get( |             authorization_flow=Flow.objects.get( | ||||||
|                 slug="default-provider-authorization-implicit-consent" |                 slug="default-provider-authorization-implicit-consent" | ||||||
|             ), |             ), | ||||||
|             internal_host="http://localhost", |             internal_host=f"http://{self.host}", | ||||||
|             external_host="http://localhost:9000", |             external_host="http://localhost:9000", | ||||||
|             basic_auth_enabled=True, |             basic_auth_enabled=True, | ||||||
|             basic_auth_user_attribute="basic-username", |             basic_auth_user_attribute="basic-username", | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """Radius e2e tests""" | """Radius e2e tests""" | ||||||
| from dataclasses import asdict | from dataclasses import asdict | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker.client import DockerClient, from_env | from docker.client import DockerClient, from_env | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| @ -19,7 +17,6 @@ from authentik.providers.radius.models import RadiusProvider | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderRadius(SeleniumTestCase): | class TestProviderRadius(SeleniumTestCase): | ||||||
|     """Radius Outpost e2e tests""" |     """Radius Outpost e2e tests""" | ||||||
|  |  | ||||||
| @ -40,7 +37,7 @@ class TestProviderRadius(SeleniumTestCase): | |||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image=self.get_container_image("ghcr.io/goauthentik/dev-radius"), |             image=self.get_container_image("ghcr.io/goauthentik/dev-radius"), | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={"1812/udp": "1812/udp"}, | ||||||
|             environment={ |             environment={ | ||||||
|                 "AUTHENTIK_HOST": self.live_server_url, |                 "AUTHENTIK_HOST": self.live_server_url, | ||||||
|                 "AUTHENTIK_TOKEN": outpost.token.key, |                 "AUTHENTIK_TOKEN": outpost.token.key, | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| """test SAML Provider flow""" | """test SAML Provider flow""" | ||||||
| from json import loads | from json import loads | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker import DockerClient, from_env | from docker import DockerClient, from_env | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| @ -20,7 +18,6 @@ from authentik.sources.saml.processors.constants import SAML_BINDING_POST | |||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestProviderSAML(SeleniumTestCase): | class TestProviderSAML(SeleniumTestCase): | ||||||
|     """test SAML Provider flow""" |     """test SAML Provider flow""" | ||||||
|  |  | ||||||
| @ -41,7 +38,9 @@ class TestProviderSAML(SeleniumTestCase): | |||||||
|         container = client.containers.run( |         container = client.containers.run( | ||||||
|             image="ghcr.io/beryju/saml-test-sp:1.1", |             image="ghcr.io/beryju/saml-test-sp:1.1", | ||||||
|             detach=True, |             detach=True, | ||||||
|             network_mode="host", |             ports={ | ||||||
|  |                 "9009": "9009", | ||||||
|  |             }, | ||||||
|             environment={ |             environment={ | ||||||
|                 "SP_ENTITY_ID": provider.issuer, |                 "SP_ENTITY_ID": provider.issuer, | ||||||
|                 "SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", |                 "SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", | ||||||
|  | |||||||
							
								
								
									
										141
									
								
								tests/e2e/test_source_oauth_oauth1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								tests/e2e/test_source_oauth_oauth1.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | |||||||
|  | """test OAuth Source""" | ||||||
|  | from time import sleep | ||||||
|  | from typing import Any, Optional | ||||||
|  |  | ||||||
|  | from selenium.webdriver.common.by import By | ||||||
|  | from selenium.webdriver.common.keys import Keys | ||||||
|  | from selenium.webdriver.support import expected_conditions as ec | ||||||
|  | from selenium.webdriver.support.wait import WebDriverWait | ||||||
|  |  | ||||||
|  | from authentik.blueprints.tests import apply_blueprint | ||||||
|  | from authentik.core.models import User | ||||||
|  | from authentik.flows.models import Flow | ||||||
|  | from authentik.lib.generators import generate_id, generate_key | ||||||
|  | from authentik.sources.oauth.models import OAuthSource | ||||||
|  | from authentik.sources.oauth.types.registry import SourceType, registry | ||||||
|  | from authentik.sources.oauth.views.callback import OAuthCallback | ||||||
|  | from authentik.stages.identification.models import IdentificationStage | ||||||
|  | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OAuth1Callback(OAuthCallback): | ||||||
|  |     """OAuth1 Callback with custom getters""" | ||||||
|  |  | ||||||
|  |     def get_user_id(self, info: dict[str, str]) -> str: | ||||||
|  |         return info.get("id") | ||||||
|  |  | ||||||
|  |     def get_user_enroll_context( | ||||||
|  |         self, | ||||||
|  |         info: dict[str, Any], | ||||||
|  |     ) -> dict[str, Any]: | ||||||
|  |         return { | ||||||
|  |             "username": info.get("screen_name"), | ||||||
|  |             "email": info.get("email"), | ||||||
|  |             "name": info.get("name"), | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @registry.register() | ||||||
|  | class OAUth1Type(SourceType): | ||||||
|  |     """OAuth1 Type definition""" | ||||||
|  |  | ||||||
|  |     callback_view = OAuth1Callback | ||||||
|  |     verbose_name = "OAuth1" | ||||||
|  |     name = "oauth1" | ||||||
|  |  | ||||||
|  |     request_token_url = "http://localhost:5001/oauth/request_token"  # nosec | ||||||
|  |     access_token_url = "http://localhost:5001/oauth/access_token"  # nosec | ||||||
|  |     authorization_url = "http://localhost:5001/oauth/authorize" | ||||||
|  |     profile_url = "http://localhost:5001/api/me" | ||||||
|  |     urls_customizable = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestSourceOAuth1(SeleniumTestCase): | ||||||
|  |     """Test OAuth1 Source""" | ||||||
|  |  | ||||||
|  |     def setUp(self) -> None: | ||||||
|  |         self.client_id = generate_id() | ||||||
|  |         self.client_secret = generate_key() | ||||||
|  |         self.source_slug = generate_id() | ||||||
|  |         super().setUp() | ||||||
|  |  | ||||||
|  |     def get_container_specs(self) -> Optional[dict[str, Any]]: | ||||||
|  |         return { | ||||||
|  |             "image": "ghcr.io/beryju/oauth1-test-server:v1.1", | ||||||
|  |             "detach": True, | ||||||
|  |             "ports": {"5000": "5001"}, | ||||||
|  |             "auto_remove": True, | ||||||
|  |             "environment": { | ||||||
|  |                 "OAUTH1_CLIENT_ID": self.client_id, | ||||||
|  |                 "OAUTH1_CLIENT_SECRET": self.client_secret, | ||||||
|  |                 "OAUTH1_REDIRECT_URI": self.url( | ||||||
|  |                     "authentik_sources_oauth:oauth-client-callback", | ||||||
|  |                     source_slug=self.source_slug, | ||||||
|  |                 ), | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     def create_objects(self): | ||||||
|  |         """Create required objects""" | ||||||
|  |         # Bootstrap all needed objects | ||||||
|  |         authentication_flow = Flow.objects.get(slug="default-source-authentication") | ||||||
|  |         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") | ||||||
|  |  | ||||||
|  |         source = OAuthSource.objects.create(  # nosec | ||||||
|  |             name=generate_id(), | ||||||
|  |             slug=self.source_slug, | ||||||
|  |             authentication_flow=authentication_flow, | ||||||
|  |             enrollment_flow=enrollment_flow, | ||||||
|  |             provider_type="oauth1", | ||||||
|  |             consumer_key=self.client_id, | ||||||
|  |             consumer_secret=self.client_secret, | ||||||
|  |         ) | ||||||
|  |         ident_stage = IdentificationStage.objects.first() | ||||||
|  |         ident_stage.sources.set([source]) | ||||||
|  |         ident_stage.save() | ||||||
|  |  | ||||||
|  |     @retry() | ||||||
|  |     @apply_blueprint( | ||||||
|  |         "default/flow-default-authentication-flow.yaml", | ||||||
|  |         "default/flow-default-invalidation-flow.yaml", | ||||||
|  |     ) | ||||||
|  |     @apply_blueprint( | ||||||
|  |         "default/flow-default-source-authentication.yaml", | ||||||
|  |         "default/flow-default-source-enrollment.yaml", | ||||||
|  |         "default/flow-default-source-pre-authentication.yaml", | ||||||
|  |     ) | ||||||
|  |     def test_oauth_enroll(self): | ||||||
|  |         """test OAuth Source With With OIDC""" | ||||||
|  |         self.create_objects() | ||||||
|  |         self.driver.get(self.live_server_url) | ||||||
|  |  | ||||||
|  |         flow_executor = self.get_shadow_root("ak-flow-executor") | ||||||
|  |         identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) | ||||||
|  |         wait = WebDriverWait(identification_stage, self.wait_timeout) | ||||||
|  |  | ||||||
|  |         wait.until( | ||||||
|  |             ec.presence_of_element_located( | ||||||
|  |                 (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         identification_stage.find_element( | ||||||
|  |             By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" | ||||||
|  |         ).click() | ||||||
|  |  | ||||||
|  |         # Now we should be at the IDP, wait for the login field | ||||||
|  |         self.wait.until(ec.presence_of_element_located((By.NAME, "username"))) | ||||||
|  |         self.driver.find_element(By.NAME, "username").send_keys("example-user") | ||||||
|  |         self.driver.find_element(By.NAME, "username").send_keys(Keys.ENTER) | ||||||
|  |         sleep(2) | ||||||
|  |  | ||||||
|  |         # Wait until we're logged in | ||||||
|  |         self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "[name='confirm']"))) | ||||||
|  |         self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click() | ||||||
|  |  | ||||||
|  |         # Wait until we've loaded the user info page | ||||||
|  |         sleep(2) | ||||||
|  |         # Wait until we've logged in | ||||||
|  |         self.wait_for_url(self.if_user_url("/library")) | ||||||
|  |         self.driver.get(self.if_user_url("/settings")) | ||||||
|  |  | ||||||
|  |         self.assert_user(User(username="example-user", name="test name", email="foo@example.com")) | ||||||
| @ -1,9 +1,7 @@ | |||||||
| """test OAuth Source""" | """test OAuth Source""" | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from typing import Any, Optional | from typing import Any, Optional | ||||||
| from unittest.case import skipUnless |  | ||||||
| 
 | 
 | ||||||
| from docker.models.containers import Container | from docker.models.containers import Container | ||||||
| from docker.types import Healthcheck | from docker.types import Healthcheck | ||||||
| @ -18,47 +16,12 @@ from authentik.core.models import User | |||||||
| from authentik.flows.models import Flow | from authentik.flows.models import Flow | ||||||
| from authentik.lib.generators import generate_id, generate_key | from authentik.lib.generators import generate_id, generate_key | ||||||
| from authentik.sources.oauth.models import OAuthSource | from authentik.sources.oauth.models import OAuthSource | ||||||
| from authentik.sources.oauth.types.registry import SourceType, registry |  | ||||||
| from authentik.sources.oauth.views.callback import OAuthCallback |  | ||||||
| from authentik.stages.identification.models import IdentificationStage | from authentik.stages.identification.models import IdentificationStage | ||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
| 
 | 
 | ||||||
| CONFIG_PATH = "/tmp/dex.yml"  # nosec | CONFIG_PATH = "/tmp/dex.yml"  # nosec | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class OAuth1Callback(OAuthCallback): |  | ||||||
|     """OAuth1 Callback with custom getters""" |  | ||||||
| 
 |  | ||||||
|     def get_user_id(self, info: dict[str, str]) -> str: |  | ||||||
|         return info.get("id") |  | ||||||
| 
 |  | ||||||
|     def get_user_enroll_context( |  | ||||||
|         self, |  | ||||||
|         info: dict[str, Any], |  | ||||||
|     ) -> dict[str, Any]: |  | ||||||
|         return { |  | ||||||
|             "username": info.get("screen_name"), |  | ||||||
|             "email": info.get("email"), |  | ||||||
|             "name": info.get("name"), |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @registry.register() |  | ||||||
| class OAUth1Type(SourceType): |  | ||||||
|     """OAuth1 Type definition""" |  | ||||||
| 
 |  | ||||||
|     callback_view = OAuth1Callback |  | ||||||
|     name = "OAuth1" |  | ||||||
|     slug = "oauth1" |  | ||||||
| 
 |  | ||||||
|     request_token_url = "http://localhost:5000/oauth/request_token"  # nosec |  | ||||||
|     access_token_url = "http://localhost:5000/oauth/access_token"  # nosec |  | ||||||
|     authorization_url = "http://localhost:5000/oauth/authorize" |  | ||||||
|     profile_url = "http://localhost:5000/api/me" |  | ||||||
|     urls_customizable = False |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestSourceOAuth2(SeleniumTestCase): | class TestSourceOAuth2(SeleniumTestCase): | ||||||
|     """test OAuth Source flow""" |     """test OAuth Source flow""" | ||||||
| 
 | 
 | ||||||
| @ -66,6 +29,7 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
| 
 | 
 | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         self.client_secret = generate_key() |         self.client_secret = generate_key() | ||||||
|  |         self.slug = generate_id() | ||||||
|         self.prepare_dex_config() |         self.prepare_dex_config() | ||||||
|         super().setUp() |         super().setUp() | ||||||
| 
 | 
 | ||||||
| @ -83,7 +47,7 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
|                     "redirectURIs": [ |                     "redirectURIs": [ | ||||||
|                         self.url( |                         self.url( | ||||||
|                             "authentik_sources_oauth:oauth-client-callback", |                             "authentik_sources_oauth:oauth-client-callback", | ||||||
|                             source_slug="dex", |                             source_slug=self.slug, | ||||||
|                         ) |                         ) | ||||||
|                     ], |                     ], | ||||||
|                     "secret": self.client_secret, |                     "secret": self.client_secret, | ||||||
| @ -108,7 +72,7 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
|         return { |         return { | ||||||
|             "image": "ghcr.io/dexidp/dex:v2.28.1", |             "image": "ghcr.io/dexidp/dex:v2.28.1", | ||||||
|             "detach": True, |             "detach": True, | ||||||
|             "network_mode": "host", |             "ports": {"5556": "5556"}, | ||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "command": "dex serve /config.yml", |             "command": "dex serve /config.yml", | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
| @ -126,8 +90,8 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
|         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") |         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") | ||||||
| 
 | 
 | ||||||
|         source = OAuthSource.objects.create(  # nosec |         source = OAuthSource.objects.create(  # nosec | ||||||
|             name="dex", |             name=generate_id(), | ||||||
|             slug="dex", |             slug=self.slug, | ||||||
|             authentication_flow=authentication_flow, |             authentication_flow=authentication_flow, | ||||||
|             enrollment_flow=enrollment_flow, |             enrollment_flow=enrollment_flow, | ||||||
|             provider_type="openidconnect", |             provider_type="openidconnect", | ||||||
| @ -229,95 +193,3 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
|         self.driver.get(self.if_user_url("/settings")) |         self.driver.get(self.if_user_url("/settings")) | ||||||
| 
 | 
 | ||||||
|         self.assert_user(User(username="foo", name="admin", email="admin@example.com")) |         self.assert_user(User(username="foo", name="admin", email="admin@example.com")) | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestSourceOAuth1(SeleniumTestCase): |  | ||||||
|     """Test OAuth1 Source""" |  | ||||||
| 
 |  | ||||||
|     def setUp(self) -> None: |  | ||||||
|         self.client_id = generate_id() |  | ||||||
|         self.client_secret = generate_key() |  | ||||||
|         self.source_slug = "oauth1-test" |  | ||||||
|         super().setUp() |  | ||||||
| 
 |  | ||||||
|     def get_container_specs(self) -> Optional[dict[str, Any]]: |  | ||||||
|         return { |  | ||||||
|             "image": "ghcr.io/beryju/oauth1-test-server:v1.1", |  | ||||||
|             "detach": True, |  | ||||||
|             "network_mode": "host", |  | ||||||
|             "auto_remove": True, |  | ||||||
|             "environment": { |  | ||||||
|                 "OAUTH1_CLIENT_ID": self.client_id, |  | ||||||
|                 "OAUTH1_CLIENT_SECRET": self.client_secret, |  | ||||||
|                 "OAUTH1_REDIRECT_URI": self.url( |  | ||||||
|                     "authentik_sources_oauth:oauth-client-callback", |  | ||||||
|                     source_slug=self.source_slug, |  | ||||||
|                 ), |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     def create_objects(self): |  | ||||||
|         """Create required objects""" |  | ||||||
|         # Bootstrap all needed objects |  | ||||||
|         authentication_flow = Flow.objects.get(slug="default-source-authentication") |  | ||||||
|         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") |  | ||||||
| 
 |  | ||||||
|         source = OAuthSource.objects.create(  # nosec |  | ||||||
|             name="oauth1", |  | ||||||
|             slug=self.source_slug, |  | ||||||
|             authentication_flow=authentication_flow, |  | ||||||
|             enrollment_flow=enrollment_flow, |  | ||||||
|             provider_type="oauth1", |  | ||||||
|             consumer_key=self.client_id, |  | ||||||
|             consumer_secret=self.client_secret, |  | ||||||
|         ) |  | ||||||
|         ident_stage = IdentificationStage.objects.first() |  | ||||||
|         ident_stage.sources.set([source]) |  | ||||||
|         ident_stage.save() |  | ||||||
| 
 |  | ||||||
|     @retry() |  | ||||||
|     @apply_blueprint( |  | ||||||
|         "default/flow-default-authentication-flow.yaml", |  | ||||||
|         "default/flow-default-invalidation-flow.yaml", |  | ||||||
|     ) |  | ||||||
|     @apply_blueprint( |  | ||||||
|         "default/flow-default-source-authentication.yaml", |  | ||||||
|         "default/flow-default-source-enrollment.yaml", |  | ||||||
|         "default/flow-default-source-pre-authentication.yaml", |  | ||||||
|     ) |  | ||||||
|     def test_oauth_enroll(self): |  | ||||||
|         """test OAuth Source With With OIDC""" |  | ||||||
|         self.create_objects() |  | ||||||
|         self.driver.get(self.live_server_url) |  | ||||||
| 
 |  | ||||||
|         flow_executor = self.get_shadow_root("ak-flow-executor") |  | ||||||
|         identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) |  | ||||||
|         wait = WebDriverWait(identification_stage, self.wait_timeout) |  | ||||||
| 
 |  | ||||||
|         wait.until( |  | ||||||
|             ec.presence_of_element_located( |  | ||||||
|                 (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         identification_stage.find_element( |  | ||||||
|             By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" |  | ||||||
|         ).click() |  | ||||||
| 
 |  | ||||||
|         # Now we should be at the IDP, wait for the login field |  | ||||||
|         self.wait.until(ec.presence_of_element_located((By.NAME, "username"))) |  | ||||||
|         self.driver.find_element(By.NAME, "username").send_keys("example-user") |  | ||||||
|         self.driver.find_element(By.NAME, "username").send_keys(Keys.ENTER) |  | ||||||
|         sleep(2) |  | ||||||
| 
 |  | ||||||
|         # Wait until we're logged in |  | ||||||
|         self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "[name='confirm']"))) |  | ||||||
|         self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click() |  | ||||||
| 
 |  | ||||||
|         # Wait until we've loaded the user info page |  | ||||||
|         sleep(2) |  | ||||||
|         # Wait until we've logged in |  | ||||||
|         self.wait_for_url(self.if_user_url("/library")) |  | ||||||
|         self.driver.get(self.if_user_url("/settings")) |  | ||||||
| 
 |  | ||||||
|         self.assert_user(User(username="example-user", name="test name", email="foo@example.com")) |  | ||||||
| @ -1,8 +1,6 @@ | |||||||
| """test SAML Source""" | """test SAML Source""" | ||||||
| from sys import platform |  | ||||||
| from time import sleep | from time import sleep | ||||||
| from typing import Any, Optional | from typing import Any, Optional | ||||||
| from unittest.case import skipUnless |  | ||||||
|  |  | ||||||
| from docker.types import Healthcheck | from docker.types import Healthcheck | ||||||
| from guardian.utils import get_anonymous_user | from guardian.utils import get_anonymous_user | ||||||
| @ -15,6 +13,7 @@ from authentik.blueprints.tests import apply_blueprint | |||||||
| from authentik.core.models import User | from authentik.core.models import User | ||||||
| from authentik.crypto.models import CertificateKeyPair | from authentik.crypto.models import CertificateKeyPair | ||||||
| from authentik.flows.models import Flow | from authentik.flows.models import Flow | ||||||
|  | from authentik.lib.generators import generate_id | ||||||
| from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource | from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource | ||||||
| from authentik.stages.identification.models import IdentificationStage | from authentik.stages.identification.models import IdentificationStage | ||||||
| from tests.e2e.utils import SeleniumTestCase, retry | from tests.e2e.utils import SeleniumTestCase, retry | ||||||
| @ -71,15 +70,18 @@ Sm75WXsflOxuTn08LbgGc4s= | |||||||
| -----END PRIVATE KEY-----""" | -----END PRIVATE KEY-----""" | ||||||
|  |  | ||||||
|  |  | ||||||
| @skipUnless(platform.startswith("linux"), "requires local docker") |  | ||||||
| class TestSourceSAML(SeleniumTestCase): | class TestSourceSAML(SeleniumTestCase): | ||||||
|     """test SAML Source flow""" |     """test SAML Source flow""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.slug = generate_id() | ||||||
|  |         super().setUp() | ||||||
|  |  | ||||||
|     def get_container_specs(self) -> Optional[dict[str, Any]]: |     def get_container_specs(self) -> Optional[dict[str, Any]]: | ||||||
|         return { |         return { | ||||||
|             "image": "kristophjunge/test-saml-idp:1.15", |             "image": "kristophjunge/test-saml-idp:1.15", | ||||||
|             "detach": True, |             "detach": True, | ||||||
|             "network_mode": "host", |             "ports": {"8080": "8080"}, | ||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "curl", "http://localhost:8080"], |                 test=["CMD", "curl", "http://localhost:8080"], | ||||||
| @ -89,7 +91,7 @@ class TestSourceSAML(SeleniumTestCase): | |||||||
|             "environment": { |             "environment": { | ||||||
|                 "SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id", |                 "SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id", | ||||||
|                 "SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE": ( |                 "SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE": ( | ||||||
|                     f"{self.live_server_url}/source/saml/saml-idp-test/acs/" |                     self.url("authentik_sources_saml:acs", source_slug=self.slug) | ||||||
|                 ), |                 ), | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
| @ -111,19 +113,19 @@ class TestSourceSAML(SeleniumTestCase): | |||||||
|         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") |         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") | ||||||
|         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") |         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") | ||||||
|         keypair = CertificateKeyPair.objects.create( |         keypair = CertificateKeyPair.objects.create( | ||||||
|             name="test-idp-cert", |             name=generate_id(), | ||||||
|             certificate_data=IDP_CERT, |             certificate_data=IDP_CERT, | ||||||
|             key_data=IDP_KEY, |             key_data=IDP_KEY, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         source = SAMLSource.objects.create( |         source = SAMLSource.objects.create( | ||||||
|             name="saml-idp-test", |             name=generate_id(), | ||||||
|             slug="saml-idp-test", |             slug=self.slug, | ||||||
|             authentication_flow=authentication_flow, |             authentication_flow=authentication_flow, | ||||||
|             enrollment_flow=enrollment_flow, |             enrollment_flow=enrollment_flow, | ||||||
|             pre_authentication_flow=pre_authentication_flow, |             pre_authentication_flow=pre_authentication_flow, | ||||||
|             issuer="entity-id", |             issuer="entity-id", | ||||||
|             sso_url="http://localhost:8080/simplesaml/saml2/idp/SSOService.php", |             sso_url=f"http://{self.host}:8080/simplesaml/saml2/idp/SSOService.php", | ||||||
|             binding_type=SAMLBindingTypes.REDIRECT, |             binding_type=SAMLBindingTypes.REDIRECT, | ||||||
|             signing_kp=keypair, |             signing_kp=keypair, | ||||||
|         ) |         ) | ||||||
| @ -181,19 +183,19 @@ class TestSourceSAML(SeleniumTestCase): | |||||||
|         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") |         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") | ||||||
|         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") |         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") | ||||||
|         keypair = CertificateKeyPair.objects.create( |         keypair = CertificateKeyPair.objects.create( | ||||||
|             name="test-idp-cert", |             name=generate_id(), | ||||||
|             certificate_data=IDP_CERT, |             certificate_data=IDP_CERT, | ||||||
|             key_data=IDP_KEY, |             key_data=IDP_KEY, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         source = SAMLSource.objects.create( |         source = SAMLSource.objects.create( | ||||||
|             name="saml-idp-test", |             name=generate_id(), | ||||||
|             slug="saml-idp-test", |             slug=self.slug, | ||||||
|             authentication_flow=authentication_flow, |             authentication_flow=authentication_flow, | ||||||
|             enrollment_flow=enrollment_flow, |             enrollment_flow=enrollment_flow, | ||||||
|             pre_authentication_flow=pre_authentication_flow, |             pre_authentication_flow=pre_authentication_flow, | ||||||
|             issuer="entity-id", |             issuer="entity-id", | ||||||
|             sso_url="http://localhost:8080/simplesaml/saml2/idp/SSOService.php", |             sso_url=f"http://{self.host}:8080/simplesaml/saml2/idp/SSOService.php", | ||||||
|             binding_type=SAMLBindingTypes.POST, |             binding_type=SAMLBindingTypes.POST, | ||||||
|             signing_kp=keypair, |             signing_kp=keypair, | ||||||
|         ) |         ) | ||||||
| @ -264,19 +266,19 @@ class TestSourceSAML(SeleniumTestCase): | |||||||
|         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") |         enrollment_flow = Flow.objects.get(slug="default-source-enrollment") | ||||||
|         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") |         pre_authentication_flow = Flow.objects.get(slug="default-source-pre-authentication") | ||||||
|         keypair = CertificateKeyPair.objects.create( |         keypair = CertificateKeyPair.objects.create( | ||||||
|             name="test-idp-cert", |             name=generate_id(), | ||||||
|             certificate_data=IDP_CERT, |             certificate_data=IDP_CERT, | ||||||
|             key_data=IDP_KEY, |             key_data=IDP_KEY, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         source = SAMLSource.objects.create( |         source = SAMLSource.objects.create( | ||||||
|             name="saml-idp-test", |             name=generate_id(), | ||||||
|             slug="saml-idp-test", |             slug=self.slug, | ||||||
|             authentication_flow=authentication_flow, |             authentication_flow=authentication_flow, | ||||||
|             enrollment_flow=enrollment_flow, |             enrollment_flow=enrollment_flow, | ||||||
|             pre_authentication_flow=pre_authentication_flow, |             pre_authentication_flow=pre_authentication_flow, | ||||||
|             issuer="entity-id", |             issuer="entity-id", | ||||||
|             sso_url="http://localhost:8080/simplesaml/saml2/idp/SSOService.php", |             sso_url=f"http://{self.host}:8080/simplesaml/saml2/idp/SSOService.php", | ||||||
|             binding_type=SAMLBindingTypes.POST_AUTO, |             binding_type=SAMLBindingTypes.POST_AUTO, | ||||||
|             signing_kp=keypair, |             signing_kp=keypair, | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| """authentik e2e testing utilities""" | """authentik e2e testing utilities""" | ||||||
| import json | import json | ||||||
| import os | import os | ||||||
|  | import socket | ||||||
| from functools import lru_cache, wraps | from functools import lru_cache, wraps | ||||||
| from os import environ | from os import environ | ||||||
| from sys import stderr | from sys import stderr | ||||||
| @ -43,6 +44,13 @@ def get_docker_tag() -> str: | |||||||
|     return f"gh-{branch_name}" |     return f"gh-{branch_name}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_local_ip() -> str: | ||||||
|  |     """Get the local machine's IP""" | ||||||
|  |     hostname = socket.gethostname() | ||||||
|  |     ip_addr = socket.gethostbyname(hostname) | ||||||
|  |     return ip_addr | ||||||
|  |  | ||||||
|  |  | ||||||
| class DockerTestCase: | class DockerTestCase: | ||||||
|     """Mixin for dealing with containers""" |     """Mixin for dealing with containers""" | ||||||
|  |  | ||||||
| @ -63,6 +71,7 @@ class DockerTestCase: | |||||||
| class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): | ||||||
|     """StaticLiveServerTestCase which automatically creates a Webdriver instance""" |     """StaticLiveServerTestCase which automatically creates a Webdriver instance""" | ||||||
|  |  | ||||||
|  |     host = get_local_ip() | ||||||
|     container: Optional[Container] = None |     container: Optional[Container] = None | ||||||
|     wait_timeout: int |     wait_timeout: int | ||||||
|     user: User |     user: User | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L