providers/oauth2: offline access (#8026)

* improve scope check (log when application requests non-configured scopes)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add offline_access special scope

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* ensure scope is set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests for refresh tokens

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* special handling of scopes for github compat

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix spec

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* attempt to fix oidc tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove hardcoded slug

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* check scope from authorization code instead of request

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix injection for consent stage checking incorrectly

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-04 19:57:11 +01:00
committed by GitHub
parent 1b36cb8331
commit 509b502d3c
15 changed files with 369 additions and 171 deletions

View File

@ -74,7 +74,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_id=self.client_id,
client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL,
@ -82,8 +82,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow,
)
Application.objects.create(
name="Grafana",
slug="grafana",
name=generate_id(),
slug=generate_id(),
provider=provider,
)
@ -129,7 +129,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_id=self.client_id,
client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL,
@ -137,8 +137,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow,
)
app = Application.objects.create(
name="Grafana",
slug="grafana",
name=generate_id(),
slug=generate_id(),
provider=provider,
)
@ -200,7 +200,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_id=self.client_id,
client_secret=self.client_secret,
client_type=ClientTypes.CONFIDENTIAL,
@ -208,8 +208,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
authorization_flow=authorization_flow,
)
app = Application.objects.create(
name="Grafana",
slug="grafana",
name=generate_id(),
slug=generate_id(),
provider=provider,
)

View File

@ -14,6 +14,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
@ -80,7 +81,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
client_secret=self.client_secret,
@ -90,12 +91,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
Application.objects.create(
name="Grafana",
name=generate_id(),
slug=self.app_slug,
provider=provider,
)
@ -113,12 +119,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self):
"""test OpenID Provider flow (default authorization flow with implied consent)"""
@ -128,7 +130,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
client_secret=self.client_secret,
@ -138,11 +140,16 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
Application.objects.create(
name="Grafana",
name=generate_id(),
slug=self.app_slug,
provider=provider,
)
@ -174,12 +181,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_logout(self):
"""test OpenID Provider flow with logout"""
@ -189,7 +192,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-implicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
client_secret=self.client_secret,
@ -199,12 +202,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
Application.objects.create(
name="Grafana",
name=generate_id(),
slug=self.app_slug,
provider=provider,
)
@ -244,12 +252,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
@ -259,7 +263,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
authorization_flow=authorization_flow,
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
@ -269,12 +273,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
app = Application.objects.create(
name="Grafana",
name=generate_id(),
slug=self.app_slug,
provider=provider,
)
@ -323,12 +332,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@apply_blueprint(
"system/providers-oauth2.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)"""
@ -338,7 +343,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
slug="default-provider-authorization-explicit-consent"
)
provider = OAuth2Provider.objects.create(
name="grafana",
name=generate_id(),
authorization_flow=authorization_flow,
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
@ -348,12 +353,17 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
app = Application.objects.create(
name="Grafana",
name=generate_id(),
slug=self.app_slug,
provider=provider,
)

View File

@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
@ -29,7 +30,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
def setUp(self):
self.client_id = generate_id()
self.client_secret = generate_key()
self.application_slug = "test"
self.application_slug = generate_id()
super().setUp()
def setup_client(self) -> Container:
@ -37,7 +38,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
sleep(1)
client: DockerClient = from_env()
container = client.containers.run(
image="ghcr.io/beryju/oidc-test-client:1.3",
image="ghcr.io/beryju/oidc-test-client:2.1",
detach=True,
ports={
"9009": "9009",
@ -56,9 +57,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@reconcile_app("authentik_crypto")
def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
@ -78,10 +77,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
Application.objects.create(
name=self.application_slug,
slug=self.application_slug,
@ -101,13 +104,12 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self):
"""test OpenID Provider flow (default authorization flow with implied consent)"""
"""test OpenID Provider flow (default authorization flow with implied consent)
(due to offline_access a consent will still be triggered)"""
sleep(1)
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
@ -124,11 +126,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
Application.objects.create(
app = Application.objects.create(
name=self.application_slug,
slug=self.application_slug,
provider=provider,
@ -137,6 +143,20 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
self.driver.get("http://localhost:9009")
self.login()
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor")))
flow_executor = self.get_shadow_root("ak-flow-executor")
consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
self.assertIn(
app.name,
consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text,
)
consent_stage.find_element(
By.CSS_SELECTOR,
"[type=submit]",
).click()
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre")))
self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{"))
body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
@ -155,11 +175,9 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1)
@ -178,10 +196,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
app = Application.objects.create(
name=self.application_slug,
slug=self.application_slug,
@ -224,9 +246,8 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)"""
@ -246,10 +267,14 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
app = Application.objects.create(
name=self.application_slug,
slug=self.application_slug,

View File

@ -15,6 +15,7 @@ from authentik.lib.generators import generate_id, generate_key
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
SCOPE_OFFLINE_ACCESS,
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
@ -37,7 +38,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
sleep(1)
client: DockerClient = from_env()
container = client.containers.run(
image="ghcr.io/beryju/oidc-test-client:1.3",
image="ghcr.io/beryju/oidc-test-client:2.1",
detach=True,
ports={
"9009": "9009",
@ -56,9 +57,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
@ -78,7 +78,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
@ -101,11 +106,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_implied(self):
"""test OpenID Provider flow (default authorization flow with implied consent)"""
sleep(1)
@ -124,7 +127,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
@ -150,11 +158,9 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@reconcile_app("authentik_crypto")
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_consent_explicit(self):
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
sleep(1)
@ -173,7 +179,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()
@ -215,9 +226,8 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-explicit-consent.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-explicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
def test_authorization_denied(self):
"""test OpenID Provider flow (default authorization with access deny)"""
@ -237,7 +247,12 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
provider.save()