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:
Jens L 2024-01-01 21:08:40 +01:00 committed by GitHub
parent b778c35396
commit b84facb9fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 836 additions and 705 deletions

View file

@ -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):

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 = "*"

View file

@ -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")),
) )

View file

@ -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"],

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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",

View file

@ -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,

View file

@ -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",

View 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"))

View file

@ -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"))

View file

@ -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,
) )

View file

@ -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