From 1daba5db87519a4cb1f90ee691727b3e7d62b672 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 2 Apr 2021 14:59:58 +0200 Subject: [PATCH] sources/oauth: revamp types system, move default URLs to type Signed-off-by: Jens Langhammer --- authentik/sources/oauth/api/source.py | 45 ++++--- authentik/sources/oauth/forms.py | 138 ---------------------- authentik/sources/oauth/models.py | 59 ++------- authentik/sources/oauth/types/azure_ad.py | 18 ++- authentik/sources/oauth/types/discord.py | 18 ++- authentik/sources/oauth/types/facebook.py | 18 ++- authentik/sources/oauth/types/github.py | 18 ++- authentik/sources/oauth/types/google.py | 18 ++- authentik/sources/oauth/types/manager.py | 72 +++++++---- authentik/sources/oauth/types/oidc.py | 16 ++- authentik/sources/oauth/types/reddit.py | 18 ++- authentik/sources/oauth/types/twitter.py | 20 +++- swagger.yaml | 65 +++++++--- 13 files changed, 257 insertions(+), 266 deletions(-) delete mode 100644 authentik/sources/oauth/forms.py diff --git a/authentik/sources/oauth/api/source.py b/authentik/sources/oauth/api/source.py index 7fc13db93..72ae3be92 100644 --- a/authentik/sources/oauth/api/source.py +++ b/authentik/sources/oauth/api/source.py @@ -2,10 +2,11 @@ from django.urls.base import reverse_lazy from drf_yasg.utils import swagger_auto_schema from rest_framework.decorators import action -from rest_framework.fields import CharField, SerializerMethodField +from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet +from drf_yasg.utils import swagger_serializer_method from authentik.core.api.sources import SourceSerializer from authentik.core.api.utils import PassiveSerializer @@ -13,6 +14,18 @@ from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.types.manager import MANAGER +class SourceTypeSerializer(PassiveSerializer): + """Serializer for SourceType""" + + name = CharField(required=True) + slug = CharField(required=True) + urls_customizable = BooleanField() + request_token_url = CharField(read_only=True, allow_null=True) + authorization_url = CharField(read_only=True, allow_null=True) + access_token_url = CharField(read_only=True, allow_null=True) + profile_url = CharField(read_only=True, allow_null=True) + + class OAuthSourceSerializer(SourceSerializer): """OAuth Source Serializer""" @@ -28,6 +41,13 @@ class OAuthSourceSerializer(SourceSerializer): return relative_url return self.context["request"].build_absolute_uri(relative_url) + type = SerializerMethodField() + + @swagger_serializer_method(serializer_or_field=SourceTypeSerializer) + def get_type(self, instace: OAuthSource) -> SourceTypeSerializer: + """Get source's type configuration""" + return SourceTypeSerializer(instace.type).data + class Meta: model = OAuthSource fields = SourceSerializer.Meta.fields + [ @@ -39,17 +59,11 @@ class OAuthSourceSerializer(SourceSerializer): "consumer_key", "consumer_secret", "callback_url", + "type", ] extra_kwargs = {"consumer_secret": {"write_only": True}} -class OAuthSourceProviderType(PassiveSerializer): - """OAuth Provider""" - - name = CharField(required=True) - value = CharField(required=True) - - class OAuthSourceViewSet(ModelViewSet): """Source Viewset""" @@ -57,16 +71,11 @@ class OAuthSourceViewSet(ModelViewSet): serializer_class = OAuthSourceSerializer lookup_field = "slug" - @swagger_auto_schema(responses={200: OAuthSourceProviderType(many=True)}) + @swagger_auto_schema(responses={200: SourceTypeSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) - def provider_types(self, request: Request) -> Response: + def source_types(self, request: Request) -> Response: """Get all creatable source types""" data = [] - for key, value in MANAGER.get_name_tuple(): - data.append( - { - "name": value, - "value": key, - } - ) - return Response(OAuthSourceProviderType(data, many=True).data) + for source_type in MANAGER.get(): + data.append(SourceTypeSerializer(source_type).data) + return Response(data) diff --git a/authentik/sources/oauth/forms.py b/authentik/sources/oauth/forms.py deleted file mode 100644 index bdcd17ab1..000000000 --- a/authentik/sources/oauth/forms.py +++ /dev/null @@ -1,138 +0,0 @@ -"""authentik oauth_client forms""" - -from django import forms - -from authentik.flows.models import Flow, FlowDesignation -from authentik.sources.oauth.models import OAuthSource -from authentik.sources.oauth.types.manager import MANAGER - - -class OAuthSourceForm(forms.ModelForm): - """OAuthSource Form""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["authentication_flow"].queryset = Flow.objects.filter( - designation=FlowDesignation.AUTHENTICATION - ) - self.fields["authentication_flow"].required = True - self.fields["enrollment_flow"].queryset = Flow.objects.filter( - designation=FlowDesignation.ENROLLMENT - ) - self.fields["enrollment_flow"].required = True - if hasattr(self.Meta, "overrides"): - for overide_field, overide_value in getattr(self.Meta, "overrides").items(): - self.fields[overide_field].initial = overide_value - self.fields[overide_field].widget.attrs["readonly"] = "readonly" - - class Meta: - - model = OAuthSource - fields = [ - "name", - "slug", - "enabled", - "policy_engine_mode", - "authentication_flow", - "enrollment_flow", - "provider_type", - "request_token_url", - "authorization_url", - "access_token_url", - "profile_url", - "consumer_key", - "consumer_secret", - ] - widgets = { - "name": forms.TextInput(), - "consumer_key": forms.TextInput(), - "consumer_secret": forms.TextInput(), - "provider_type": forms.Select(choices=MANAGER.get_name_tuple()), - } - - -class GitHubOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for GitHub""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "github", - "request_token_url": "", - "authorization_url": "https://github.com/login/oauth/authorize", - "access_token_url": "https://github.com/login/oauth/access_token", - "profile_url": "https://api.github.com/user", - } - - -class TwitterOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for Twitter""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "twitter", - "request_token_url": "https://api.twitter.com/oauth/request_token", - "authorization_url": "https://api.twitter.com/oauth/authenticate", - "access_token_url": "https://api.twitter.com/oauth/access_token", - "profile_url": ( - "https://api.twitter.com/1.1/account/" - "verify_credentials.json?include_email=true" - ), - } - - -class FacebookOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for Facebook""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "facebook", - "request_token_url": "", - "authorization_url": "https://www.facebook.com/v7.0/dialog/oauth", - "access_token_url": "https://graph.facebook.com/v7.0/oauth/access_token", - "profile_url": "https://graph.facebook.com/v7.0/me?fields=id,name,email", - } - - -class DiscordOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for Discord""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "discord", - "request_token_url": "", - "authorization_url": "https://discord.com/api/oauth2/authorize", - "access_token_url": "https://discord.com/api/oauth2/token", - "profile_url": "https://discord.com/api/users/@me", - } - - -class GoogleOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for Google""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "google", - "request_token_url": "", - "authorization_url": "https://accounts.google.com/o/oauth2/auth", - "access_token_url": "https://accounts.google.com/o/oauth2/token", - "profile_url": "https://www.googleapis.com/oauth2/v1/userinfo", - } - - -class AzureADOAuthSourceForm(OAuthSourceForm): - """OAuth Source form with pre-determined URL for AzureAD""" - - class Meta(OAuthSourceForm.Meta): - - overrides = { - "provider_type": "azure-ad", - "request_token_url": "", - "authorization_url": "https://login.microsoftonline.com/common/oauth2/authorize", - "access_token_url": "https://login.microsoftonline.com/common/oauth2/token", - "profile_url": "https://graph.windows.net/myorganization/me?api-version=1.6", - } diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py index cb9024b84..2efa8f308 100644 --- a/authentik/sources/oauth/models.py +++ b/authentik/sources/oauth/models.py @@ -1,8 +1,7 @@ """OAuth Client models""" -from typing import Optional, Type +from typing import TYPE_CHECKING, Optional, Type from django.db import models -from django.forms import ModelForm from django.templatetags.static import static from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -11,6 +10,9 @@ from rest_framework.serializers import Serializer from authentik.core.models import Source, UserSourceConnection from authentik.core.types import UILoginButton, UserSettingSerializer +if TYPE_CHECKING: + from authentik.sources.oauth.types.manager import SourceType + class OAuthSource(Source): """Login using a Generic OAuth provider.""" @@ -43,10 +45,15 @@ class OAuthSource(Source): consumer_secret = models.TextField() @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import OAuthSourceForm + def type(self) -> "SourceType": + """Return the provider instance for this source""" + from authentik.sources.oauth.types.manager import MANAGER - return OAuthSourceForm + return MANAGER.find_type(self) + + @property + def component(self) -> str: + return "ak-source-oauth-form" @property def serializer(self) -> Type[Serializer]: @@ -86,12 +93,6 @@ class OAuthSource(Source): class GitHubOAuthSource(OAuthSource): """Social Login using GitHub.com or a GitHub-Enterprise Instance.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import GitHubOAuthSourceForm - - return GitHubOAuthSourceForm - class Meta: abstract = True @@ -102,12 +103,6 @@ class GitHubOAuthSource(OAuthSource): class TwitterOAuthSource(OAuthSource): """Social Login using Twitter.com""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import TwitterOAuthSourceForm - - return TwitterOAuthSourceForm - class Meta: abstract = True @@ -118,12 +113,6 @@ class TwitterOAuthSource(OAuthSource): class FacebookOAuthSource(OAuthSource): """Social Login using Facebook.com.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import FacebookOAuthSourceForm - - return FacebookOAuthSourceForm - class Meta: abstract = True @@ -134,12 +123,6 @@ class FacebookOAuthSource(OAuthSource): class DiscordOAuthSource(OAuthSource): """Social Login using Discord.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import DiscordOAuthSourceForm - - return DiscordOAuthSourceForm - class Meta: abstract = True @@ -150,12 +133,6 @@ class DiscordOAuthSource(OAuthSource): class GoogleOAuthSource(OAuthSource): """Social Login using Google or Gsuite.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import GoogleOAuthSourceForm - - return GoogleOAuthSourceForm - class Meta: abstract = True @@ -166,12 +143,6 @@ class GoogleOAuthSource(OAuthSource): class AzureADOAuthSource(OAuthSource): """Social Login using Azure AD.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import AzureADOAuthSourceForm - - return AzureADOAuthSourceForm - class Meta: abstract = True @@ -182,12 +153,6 @@ class AzureADOAuthSource(OAuthSource): class OpenIDOAuthSource(OAuthSource): """Login using a Generic OpenID-Connect compliant provider.""" - @property - def form(self) -> Type[ModelForm]: - from authentik.sources.oauth.forms import OAuthSourceForm - - return OAuthSourceForm - class Meta: abstract = True diff --git a/authentik/sources/oauth/types/azure_ad.py b/authentik/sources/oauth/types/azure_ad.py index 697bc94fa..1e23516d4 100644 --- a/authentik/sources/oauth/types/azure_ad.py +++ b/authentik/sources/oauth/types/azure_ad.py @@ -3,11 +3,10 @@ from typing import Any from uuid import UUID from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback -@MANAGER.source(kind=RequestKind.CALLBACK, name="Azure AD") class AzureADOAuthCallback(OAuthCallback): """AzureAD OAuth2 Callback""" @@ -26,3 +25,18 @@ class AzureADOAuthCallback(OAuthCallback): "email": mail, "name": info.get("displayName"), } + + +@MANAGER.type() +class AzureADType(SourceType): + """Azure AD Type definition""" + + callback_view = AzureADOAuthCallback + name = "Azure AD" + slug = "azure-ad" + + urls_customizable = True + + authorization_url = "https://login.microsoftonline.com/common/oauth2/authorize" + access_token_url = "https://login.microsoftonline.com/common/oauth2/token" # nosec + profile_url = "https://graph.windows.net/myorganization/me?api-version=1.6" diff --git a/authentik/sources/oauth/types/discord.py b/authentik/sources/oauth/types/discord.py index b50aafa77..00bac79fd 100644 --- a/authentik/sources/oauth/types/discord.py +++ b/authentik/sources/oauth/types/discord.py @@ -2,12 +2,11 @@ from typing import Any from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect -@MANAGER.source(kind=RequestKind.REDIRECT, name="Discord") class DiscordOAuthRedirect(OAuthRedirect): """Discord OAuth2 Redirect""" @@ -17,7 +16,6 @@ class DiscordOAuthRedirect(OAuthRedirect): } -@MANAGER.source(kind=RequestKind.CALLBACK, name="Discord") class DiscordOAuth2Callback(OAuthCallback): """Discord OAuth2 Callback""" @@ -32,3 +30,17 @@ class DiscordOAuth2Callback(OAuthCallback): "email": info.get("email", None), "name": info.get("username"), } + + +@MANAGER.type() +class DiscordType(SourceType): + """Discord Type definition""" + + callback_view = DiscordOAuth2Callback + redirect_view = DiscordOAuthRedirect + name = "Discord" + slug = "discord" + + authorization_url = "https://discord.com/api/oauth2/authorize" + access_token_url = "https://discord.com/api/oauth2/token" # nosec + profile_url = "https://discord.com/api/users/@me" diff --git a/authentik/sources/oauth/types/facebook.py b/authentik/sources/oauth/types/facebook.py index 3956413da..ab27d6b6f 100644 --- a/authentik/sources/oauth/types/facebook.py +++ b/authentik/sources/oauth/types/facebook.py @@ -5,12 +5,11 @@ from facebook import GraphAPI from authentik.sources.oauth.clients.oauth2 import OAuth2Client from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect -@MANAGER.source(kind=RequestKind.REDIRECT, name="Facebook") class FacebookOAuthRedirect(OAuthRedirect): """Facebook OAuth2 Redirect""" @@ -28,7 +27,6 @@ class FacebookOAuth2Client(OAuth2Client): return api.get_object("me", fields="id,name,email") -@MANAGER.source(kind=RequestKind.CALLBACK, name="Facebook") class FacebookOAuth2Callback(OAuthCallback): """Facebook OAuth2 Callback""" @@ -45,3 +43,17 @@ class FacebookOAuth2Callback(OAuthCallback): "email": info.get("email"), "name": info.get("name"), } + + +@MANAGER.type() +class FacebookType(SourceType): + """Facebook Type definition""" + + callback_view = FacebookOAuth2Callback + redirect_view = FacebookOAuthRedirect + name = "Facebook" + slug = "facebook" + + authorization_url = "https://www.facebook.com/v7.0/dialog/oauth" + access_token_url = "https://graph.facebook.com/v7.0/oauth/access_token" # nosec + profile_url = "https://graph.facebook.com/v7.0/me?fields=id,name,email" diff --git a/authentik/sources/oauth/types/github.py b/authentik/sources/oauth/types/github.py index 420fa6ba1..c830d4919 100644 --- a/authentik/sources/oauth/types/github.py +++ b/authentik/sources/oauth/types/github.py @@ -2,11 +2,10 @@ from typing import Any from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback -@MANAGER.source(kind=RequestKind.CALLBACK, name="GitHub") class GitHubOAuth2Callback(OAuthCallback): """GitHub OAuth2 Callback""" @@ -21,3 +20,18 @@ class GitHubOAuth2Callback(OAuthCallback): "email": info.get("email"), "name": info.get("name"), } + + +@MANAGER.type() +class GitHubType(SourceType): + """GitHub Type definition""" + + callback_view = GitHubOAuth2Callback + name = "GitHub" + slug = "github" + + urls_customizable = True + + authorization_url = "https://github.com/login/oauth/authorize" + access_token_url = "https://github.com/login/oauth/access_token" # nosec + profile_url = "https://api.github.com/user" diff --git a/authentik/sources/oauth/types/google.py b/authentik/sources/oauth/types/google.py index c7d1ba8c8..e69004254 100644 --- a/authentik/sources/oauth/types/google.py +++ b/authentik/sources/oauth/types/google.py @@ -2,12 +2,11 @@ from typing import Any from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect -@MANAGER.source(kind=RequestKind.REDIRECT, name="Google") class GoogleOAuthRedirect(OAuthRedirect): """Google OAuth2 Redirect""" @@ -17,7 +16,6 @@ class GoogleOAuthRedirect(OAuthRedirect): } -@MANAGER.source(kind=RequestKind.CALLBACK, name="Google") class GoogleOAuth2Callback(OAuthCallback): """Google OAuth2 Callback""" @@ -32,3 +30,17 @@ class GoogleOAuth2Callback(OAuthCallback): "email": info.get("email"), "name": info.get("name"), } + + +@MANAGER.type() +class GoogleType(SourceType): + """Google Type definition""" + + callback_view = GoogleOAuth2Callback + redirect_view = GoogleOAuthRedirect + name = "Google" + slug = "google" + + authorization_url = "https://accounts.google.com/o/oauth2/auth" + access_token_url = "https://accounts.google.com/o/oauth2/token" # nosec + profile_url = "https://www.googleapis.com/oauth2/v1/userinfo" diff --git a/authentik/sources/oauth/types/manager.py b/authentik/sources/oauth/types/manager.py index ea51b7c5b..b1b921912 100644 --- a/authentik/sources/oauth/types/manager.py +++ b/authentik/sources/oauth/types/manager.py @@ -1,16 +1,17 @@ """Source type manager""" from enum import Enum -from typing import Callable +from typing import TYPE_CHECKING, Callable, Optional -from django.utils.text import slugify from structlog.stdlib import get_logger -from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect LOGGER = get_logger() +if TYPE_CHECKING: + from authentik.sources.oauth.models import OAuthSource + class RequestKind(Enum): """Enum of OAuth Request types""" @@ -19,46 +20,67 @@ class RequestKind(Enum): REDIRECT = "redirect" +class SourceType: + """Source type, allows overriding of urls and views per type""" + + callback_view = OAuthCallback + redirect_view = OAuthRedirect + name: str + slug: str + + urls_customizable = False + + request_token_url: Optional[str] = None + authorization_url: Optional[str] = None + access_token_url: Optional[str] = None + profile_url: Optional[str] = None + + class SourceTypeManager: """Manager to hold all Source types.""" - __source_types: dict[RequestKind, dict[str, Callable]] = {} - __names: list[str] = [] + __sources: list[SourceType] = [] - def source(self, kind: RequestKind, name: str): + def type(self): """Class decorator to register classes inline.""" def inner_wrapper(cls): - if kind.value not in self.__source_types: - self.__source_types[kind.value] = {} - self.__source_types[kind.value][slugify(name)] = cls - self.__names.append(name) + self.__sources.append(cls) return cls return inner_wrapper + def get(self): + """Get a list of all source types""" + return self.__sources + def get_name_tuple(self): """Get list of tuples of all registered names""" - return [(slugify(x), x) for x in set(self.__names)] + return [(x.slug, x.name) for x in self.__sources] - def find(self, source: OAuthSource, kind: RequestKind) -> Callable: - """Find fitting Source Type""" - if kind.value in self.__source_types: - if source.provider_type in self.__source_types[kind.value]: - return self.__source_types[kind.value][source.provider_type] + def find_type(self, source: "OAuthSource") -> SourceType: + """Find type based on source""" + found_type = None + for src_type in self.__sources: + if src_type.slug == source.provider_type: + return src_type + if not found_type: + found_type = SourceType() LOGGER.warning( "no matching type found, using default", wanted=source.provider_type, - have=self.__source_types[kind.value].keys(), + have=[x.name for x in self.__sources], ) - # Return defaults - if kind == RequestKind.CALLBACK: - return OAuthCallback - if kind == RequestKind.REDIRECT: - return OAuthRedirect - raise KeyError( - f"Provider Type {source.provider_type} (type {kind.value}) not found." - ) + return found_type + + def find(self, source: "OAuthSource", kind: RequestKind) -> Callable: + """Find fitting Source Type""" + found_type = self.find_type(source) + if kind == RequestKind.CALLBACK: + return found_type.callback_view + if kind == RequestKind.REDIRECT: + return found_type.redirect_view + raise ValueError MANAGER = SourceTypeManager() diff --git a/authentik/sources/oauth/types/oidc.py b/authentik/sources/oauth/types/oidc.py index 7fafaead2..e2acf4b63 100644 --- a/authentik/sources/oauth/types/oidc.py +++ b/authentik/sources/oauth/types/oidc.py @@ -2,12 +2,11 @@ from typing import Any from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect -@MANAGER.source(kind=RequestKind.REDIRECT, name="OpenID Connect") class OpenIDConnectOAuthRedirect(OAuthRedirect): """OpenIDConnect OAuth2 Redirect""" @@ -17,7 +16,6 @@ class OpenIDConnectOAuthRedirect(OAuthRedirect): } -@MANAGER.source(kind=RequestKind.CALLBACK, name="OpenID Connect") class OpenIDConnectOAuth2Callback(OAuthCallback): """OpenIDConnect OAuth2 Callback""" @@ -35,3 +33,15 @@ class OpenIDConnectOAuth2Callback(OAuthCallback): "email": info.get("email"), "name": info.get("name"), } + + +@MANAGER.type() +class OpenIDConnectType(SourceType): + """OpenIDConnect Type definition""" + + callback_view = OpenIDConnectOAuth2Callback + redirect_view = OpenIDConnectOAuthRedirect + name = "OpenID Connect" + slug = "openid-connect" + + urls_customizable = True diff --git a/authentik/sources/oauth/types/reddit.py b/authentik/sources/oauth/types/reddit.py index 868bb23dc..74c777e6d 100644 --- a/authentik/sources/oauth/types/reddit.py +++ b/authentik/sources/oauth/types/reddit.py @@ -5,12 +5,11 @@ from requests.auth import HTTPBasicAuth from authentik.sources.oauth.clients.oauth2 import OAuth2Client from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.redirect import OAuthRedirect -@MANAGER.source(kind=RequestKind.REDIRECT, name="reddit") class RedditOAuthRedirect(OAuthRedirect): """Reddit OAuth2 Redirect""" @@ -30,7 +29,6 @@ class RedditOAuth2Client(OAuth2Client): return super().get_access_token(auth=auth) -@MANAGER.source(kind=RequestKind.CALLBACK, name="reddit") class RedditOAuth2Callback(OAuthCallback): """Reddit OAuth2 Callback""" @@ -48,3 +46,17 @@ class RedditOAuth2Callback(OAuthCallback): "name": info.get("name"), "password": None, } + + +@MANAGER.type() +class RedditType(SourceType): + """Reddit Type definition""" + + callback_view = RedditOAuth2Callback + redirect_view = RedditOAuthRedirect + name = "reddit" + slug = "reddit" + + authorization_url = "https://accounts.google.com/o/oauth2/auth" + access_token_url = "https://accounts.google.com/o/oauth2/token" # nosec + profile_url = "https://www.googleapis.com/oauth2/v1/userinfo" diff --git a/authentik/sources/oauth/types/twitter.py b/authentik/sources/oauth/types/twitter.py index ff4105ee6..df1ed1a9f 100644 --- a/authentik/sources/oauth/types/twitter.py +++ b/authentik/sources/oauth/types/twitter.py @@ -2,11 +2,10 @@ from typing import Any from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection -from authentik.sources.oauth.types.manager import MANAGER, RequestKind +from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.views.callback import OAuthCallback -@MANAGER.source(kind=RequestKind.CALLBACK, name="Twitter") class TwitterOAuthCallback(OAuthCallback): """Twitter OAuth2 Callback""" @@ -21,3 +20,20 @@ class TwitterOAuthCallback(OAuthCallback): "email": info.get("email", None), "name": info.get("name"), } + + +@MANAGER.type() +class TwitterType(SourceType): + """Twitter Type definition""" + + callback_view = TwitterOAuthCallback + name = "Twitter" + slug = "twitter" + + request_token_url = "https://api.twitter.com/oauth/request_token" # nosec + authorization_url = "https://api.twitter.com/oauth/authenticate" + access_token_url = "https://api.twitter.com/oauth/access_token" # nosec + profile_url = ( + "https://api.twitter.com/1.1/account/" + "verify_credentials.json?include_email=true" + ) diff --git a/swagger.yaml b/swagger.yaml index 5f298d076..621c52f38 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -9642,9 +9642,9 @@ paths: tags: - sources parameters: [] - /sources/oauth/provider_types/: + /sources/oauth/source_types/: get: - operationId: sources_oauth_provider_types + operationId: sources_oauth_source_types description: Get all creatable source types parameters: [] responses: @@ -9653,7 +9653,7 @@ paths: schema: type: array items: - $ref: '#/definitions/OAuthSourceProviderType' + $ref: '#/definitions/SourceType' '403': description: Authentication credentials were invalid, absent or insufficient. schema: @@ -16907,6 +16907,49 @@ definitions: type: string format: date-time readOnly: true + SourceType: + description: Get source's type configuration + required: + - name + - slug + - urls_customizable + type: object + properties: + name: + title: Name + type: string + minLength: 1 + slug: + title: Slug + type: string + minLength: 1 + urls_customizable: + title: Urls customizable + type: boolean + request_token_url: + title: Request token url + type: string + readOnly: true + minLength: 1 + x-nullable: true + authorization_url: + title: Authorization url + type: string + readOnly: true + minLength: 1 + x-nullable: true + access_token_url: + title: Access token url + type: string + readOnly: true + minLength: 1 + x-nullable: true + profile_url: + title: Profile url + type: string + readOnly: true + minLength: 1 + x-nullable: true OAuthSource: required: - name @@ -17011,20 +17054,8 @@ definitions: title: Callback url type: string readOnly: true - OAuthSourceProviderType: - required: - - name - - value - type: object - properties: - name: - title: Name - type: string - minLength: 1 - value: - title: Value - type: string - minLength: 1 + type: + $ref: '#/definitions/SourceType' UserOAuthSourceConnection: required: - user