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:
parent
b778c35396
commit
b84facb9fc
|
@ -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 a new issue