sources/oauth: add additional scopes field to get additional data from provider

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
This commit is contained in:
Jens Langhammer 2022-01-03 16:43:52 +01:00
parent a596392bc3
commit 212220554f
20 changed files with 136 additions and 13 deletions

View File

@ -74,6 +74,7 @@ class OAuthSourceSerializer(SourceSerializer):
"consumer_key",
"consumer_secret",
"callback_url",
"additional_scopes",
"type",
]
extra_kwargs = {"consumer_secret": {"write_only": True}}
@ -99,6 +100,7 @@ class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
"access_token_url",
"profile_url",
"consumer_key",
"additional_scopes",
]
ordering = ["name"]

View File

@ -58,6 +58,9 @@ class BaseOAuthClient:
args = self.get_redirect_args()
additional = parameters or {}
args.update(additional)
# Special handling for scope, since it's set as array
# to make additional scopes easier
args["scope"] = " ".join(sorted(set(args["scope"])))
params = urlencode(args, quote_via=quote)
LOGGER.info("redirect args", **args)
authorization_url = self.source.type.authorization_url or ""

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0 on 2022-01-03 14:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_oauth", "0005_update_provider_type_names"),
]
operations = [
migrations.AddField(
model_name="oauthsource",
name="additional_scopes",
field=models.TextField(default="", verbose_name="Additional Scopes"),
),
]

View File

@ -44,6 +44,7 @@ class OAuthSource(Source):
verbose_name=_("Profile URL"),
help_text=_("URL used by authentik to get user information."),
)
additional_scopes = models.TextField(default="", verbose_name=_("Additional Scopes"))
consumer_key = models.TextField()
consumer_secret = models.TextField()

View File

@ -1,8 +1,11 @@
"""google Type tests"""
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase
from django.test.client import RequestFactory
from authentik.lib.tests.utils import dummy_get_response
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.google import GoogleOAuth2Callback
from authentik.sources.oauth.types.google import GoogleOAuth2Callback, GoogleOAuthRedirect
# https://developers.google.com/identity/protocols/oauth2/openid-connect?hl=en
GOOGLE_USER = {
@ -21,17 +24,51 @@ class TestTypeGoogle(TestCase):
"""OAuth Source tests"""
def setUp(self):
self.source = OAuthSource.objects.create(
self.source: OAuthSource = OAuthSource.objects.create(
name="test",
slug="test",
provider_type="google",
authorization_url="",
profile_url="",
consumer_key="",
consumer_key="foo",
)
self.request_factory = RequestFactory()
def test_enroll_context(self):
"""Test Google Enrollment context"""
ak_context = GoogleOAuth2Callback().get_user_enroll_context(GOOGLE_USER)
self.assertEqual(ak_context["email"], GOOGLE_USER["email"])
self.assertEqual(ak_context["name"], GOOGLE_USER["name"])
def test_authorize_url(self):
"""Test authorize URL"""
request = self.request_factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
redirect = GoogleOAuthRedirect(request=request).get_redirect_url(
source_slug=self.source.slug
)
self.assertEqual(
redirect,
(
f"https://accounts.google.com/o/oauth2/auth?client_id={self.source.consumer_key}&re"
"direct_uri=http%3A%2F%2Ftestserver%2Fsource%2Foauth%2Fcallback%2Ftest%2F&response_"
f"type=code&state={request.session['oauth-client-test-request-state']}&scope="
"email%20profile"
),
)
self.source.additional_scopes = "foo"
self.source.save()
redirect = GoogleOAuthRedirect(request=request).get_redirect_url(
source_slug=self.source.slug
)
self.assertEqual(
redirect,
(
f"https://accounts.google.com/o/oauth2/auth?client_id={self.source.consumer_key}&re"
"direct_uri=http%3A%2F%2Ftestserver%2Fsource%2Foauth%2Fcallback%2Ftest%2F&response_"
f"type=code&state={request.session['oauth-client-test-request-state']}&scope="
"email%20foo%20profile"
),
)

View File

@ -78,7 +78,7 @@ class AppleOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
return {
"scope": "name email",
"scope": ["name", "email"],
"response_mode": "form_post",
}

View File

@ -17,7 +17,7 @@ class AzureADOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "openid https://graph.microsoft.com/User.Read",
"scope": ["openid", "https://graph.microsoft.com/User.Read"],
}

View File

@ -11,7 +11,7 @@ class DiscordOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "email identify",
"scope": ["email", "identify"],
"prompt": "none",
}

View File

@ -14,7 +14,7 @@ class FacebookOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "email",
"scope": ["email"],
}

View File

@ -11,7 +11,7 @@ class GoogleOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "email profile",
"scope": ["email", "profile"],
}

View File

@ -12,7 +12,7 @@ class OpenIDConnectOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
return {
"scope": "openid email profile",
"scope": ["openid", "email", "profile"],
}

View File

@ -13,7 +13,7 @@ class OktaOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
return {
"scope": "openid email profile",
"scope": ["openid", "email", "profile"],
}

View File

@ -14,7 +14,7 @@ class RedditOAuthRedirect(OAuthRedirect):
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "identity",
"scope": ["identity"],
"duration": "permanent",
}

View File

@ -34,7 +34,7 @@ class OAuthRedirect(OAuthClientMixin, RedirectView):
"Build redirect url for a given source."
slug = kwargs.get("source_slug", "")
try:
source = OAuthSource.objects.get(slug=slug)
source: OAuthSource = OAuthSource.objects.get(slug=slug)
except OAuthSource.DoesNotExist:
raise Http404(f"Unknown OAuth source '{slug}'.")
else:
@ -42,4 +42,7 @@ class OAuthRedirect(OAuthClientMixin, RedirectView):
raise Http404(f"source {slug} is not enabled.")
client = self.get_client(source, callback=self.get_callback_url(source))
params = self.get_additional_parameters(source)
params.setdefault("scope", [])
if source.additional_scopes != "":
params["scope"] += source.additional_scopes.split(" ")
return client.get_redirect_url(params)

View File

@ -12437,6 +12437,10 @@ paths:
name: access_token_url
schema:
type: string
- in: query
name: additional_scopes
schema:
type: string
- in: query
name: authentication_flow
schema:
@ -23342,6 +23346,8 @@ components:
callback_url:
type: string
readOnly: true
additional_scopes:
type: string
type:
allOf:
- $ref: '#/components/schemas/SourceType'
@ -23425,6 +23431,9 @@ components:
type: string
writeOnly: true
minLength: 1
additional_scopes:
type: string
minLength: 1
required:
- consumer_key
- consumer_secret
@ -27645,6 +27654,9 @@ components:
type: string
writeOnly: true
minLength: 1
additional_scopes:
type: string
minLength: 1
PatchedOutpostRequest:
type: object
description: Outpost Serializer

View File

@ -208,6 +208,10 @@ msgstr "Addition Group DN"
msgid "Addition User DN"
msgstr "Addition User DN"
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional Scope"
msgstr "Additional Scope"
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional group DN, prepended to the Base DN."
msgstr "Additional group DN, prepended to the Base DN."
@ -216,6 +220,10 @@ msgstr "Additional group DN, prepended to the Base DN."
msgid "Additional scope mappings, which are passed to the proxy."
msgstr "Additional scope mappings, which are passed to the proxy."
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional scopes to be passed to the OAuth Provider, separated by space."
msgstr "Additional scopes to be passed to the OAuth Provider, separated by space."
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional settings"
msgstr "Additional settings"

View File

@ -213,6 +213,10 @@ msgstr "Préfixe DN groupes"
msgid "Addition User DN"
msgstr "Préfixe DN utilisateurs"
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional Scope"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional group DN, prepended to the Base DN."
msgstr "DN à préfixer au DN de base pour les groupes"
@ -221,6 +225,10 @@ msgstr "DN à préfixer au DN de base pour les groupes"
msgid "Additional scope mappings, which are passed to the proxy."
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional scopes to be passed to the OAuth Provider, separated by space."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional settings"
msgstr "Paramètres supplémentaire"

View File

@ -208,6 +208,10 @@ msgstr ""
msgid "Addition User DN"
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional Scope"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional group DN, prepended to the Base DN."
msgstr ""
@ -216,6 +220,10 @@ msgstr ""
msgid "Additional scope mappings, which are passed to the proxy."
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional scopes to be passed to the OAuth Provider, separated by space."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional settings"
msgstr ""

View File

@ -210,6 +210,10 @@ msgstr "Toplama Grubu DN"
msgid "Addition User DN"
msgstr "Ekleme Kullanıcı DN"
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional Scope"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional group DN, prepended to the Base DN."
msgstr "Ek grup DN, Base DN için eklenmiş."
@ -218,6 +222,10 @@ msgstr "Ek grup DN, Base DN için eklenmiş."
msgid "Additional scope mappings, which are passed to the proxy."
msgstr "Proxy'ye iletilen ek kapsam eşlemeleri."
#: src/pages/sources/oauth/OAuthSourceForm.ts
msgid "Additional scopes to be passed to the OAuth Provider, separated by space."
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Additional settings"
msgstr "Ek ayarlar"

View File

@ -52,7 +52,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
}
@property({ attribute: false })
providerType?: SourceType;
providerType: SourceType | null = null;
getSuccessMessage(): string {
if (this.instance) {
@ -257,6 +257,21 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
</div>
</ak-form-group>
${this.renderUrlOptions()}
<ak-form-element-horizontal
label=${t`Additional Scope`}
?required=${true}
name="additionalScopes"
>
<input
type="text"
value="${first(this.instance?.additionalScopes, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Additional scopes to be passed to the OAuth Provider, separated by space.`}
</p>
</ak-form-element-horizontal>
<ak-form-group>
<span slot="header"> ${t`Flow settings`} </span>
<div slot="body" class="pf-c-form">