sources/oauth: add Sign in with Apple (#1635)
* sources/oauth: add apple sign in support Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: apple sign in docs Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: fix missing apple in sidebar Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * sources/oauth: add fallback values for name and slug Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -7,14 +7,15 @@ from structlog.stdlib import get_logger | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| AUTHENTIK_SOURCES_OAUTH_TYPES = [ | ||||
|     "authentik.sources.oauth.types.apple", | ||||
|     "authentik.sources.oauth.types.azure_ad", | ||||
|     "authentik.sources.oauth.types.discord", | ||||
|     "authentik.sources.oauth.types.facebook", | ||||
|     "authentik.sources.oauth.types.github", | ||||
|     "authentik.sources.oauth.types.google", | ||||
|     "authentik.sources.oauth.types.oidc", | ||||
|     "authentik.sources.oauth.types.reddit", | ||||
|     "authentik.sources.oauth.types.twitter", | ||||
|     "authentik.sources.oauth.types.azure_ad", | ||||
|     "authentik.sources.oauth.types.oidc", | ||||
| ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| """OAuth Clients""" | ||||
| from typing import Any, Optional | ||||
| from urllib.parse import urlencode | ||||
| from urllib.parse import quote, urlencode | ||||
|  | ||||
| from django.http import HttpRequest | ||||
| from requests import Session | ||||
| @ -58,7 +58,7 @@ class BaseOAuthClient: | ||||
|         args = self.get_redirect_args() | ||||
|         additional = parameters or {} | ||||
|         args.update(additional) | ||||
|         params = urlencode(args) | ||||
|         params = urlencode(args, quote_via=quote) | ||||
|         LOGGER.info("redirect args", **args) | ||||
|         authorization_url = self.source.type.authorization_url or "" | ||||
|         if self.source.type.urls_customizable and self.source.authorization_url: | ||||
|  | ||||
| @ -20,10 +20,16 @@ class OAuth2Client(BaseOAuthClient): | ||||
|         "Accept": "application/json", | ||||
|     } | ||||
|  | ||||
|     def get_request_arg(self, key: str, default: Optional[Any] = None) -> Any: | ||||
|         """Depending on request type, get data from post or get""" | ||||
|         if self.request.method == "POST": | ||||
|             return self.request.POST.get(key, default) | ||||
|         return self.request.GET.get(key, default) | ||||
|  | ||||
|     def check_application_state(self) -> bool: | ||||
|         "Check optional state parameter." | ||||
|         stored = self.request.session.get(self.session_key, None) | ||||
|         returned = self.request.GET.get("state", None) | ||||
|         returned = self.get_request_arg("state", None) | ||||
|         check = False | ||||
|         if stored is not None: | ||||
|             if returned is not None: | ||||
| @ -38,23 +44,31 @@ class OAuth2Client(BaseOAuthClient): | ||||
|         "Generate state optional parameter." | ||||
|         return get_random_string(32) | ||||
|  | ||||
|     def get_client_id(self) -> str: | ||||
|         """Get client id""" | ||||
|         return self.source.consumer_key | ||||
|  | ||||
|     def get_client_secret(self) -> str: | ||||
|         """Get client secret""" | ||||
|         return self.source.consumer_secret | ||||
|  | ||||
|     def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]: | ||||
|         "Fetch access token from callback request." | ||||
|         callback = self.request.build_absolute_uri(self.callback or self.request.path) | ||||
|         if not self.check_application_state(): | ||||
|             LOGGER.warning("Application state check failed.") | ||||
|             return None | ||||
|         if "code" in self.request.GET: | ||||
|             args = { | ||||
|                 "client_id": self.source.consumer_key, | ||||
|                 "redirect_uri": callback, | ||||
|                 "client_secret": self.source.consumer_secret, | ||||
|                 "code": self.request.GET["code"], | ||||
|                 "grant_type": "authorization_code", | ||||
|             } | ||||
|         else: | ||||
|         code = self.get_request_arg("code", None) | ||||
|         if not code: | ||||
|             LOGGER.warning("No code returned by the source") | ||||
|             return None | ||||
|         args = { | ||||
|             "client_id": self.get_client_id(), | ||||
|             "client_secret": self.get_client_secret(), | ||||
|             "redirect_uri": callback, | ||||
|             "code": code, | ||||
|             "grant_type": "authorization_code", | ||||
|         } | ||||
|         try: | ||||
|             access_token_url = self.source.type.access_token_url or "" | ||||
|             if self.source.type.urls_customizable and self.source.access_token_url: | ||||
| @ -75,7 +89,7 @@ class OAuth2Client(BaseOAuthClient): | ||||
|     def get_redirect_args(self) -> dict[str, str]: | ||||
|         "Get request parameters for redirect url." | ||||
|         callback = self.request.build_absolute_uri(self.callback) | ||||
|         client_id: str = self.source.consumer_key | ||||
|         client_id: str = self.get_client_id() | ||||
|         args: dict[str, str] = { | ||||
|             "client_id": client_id, | ||||
|             "redirect_uri": callback, | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| from typing import TYPE_CHECKING, Optional, Type | ||||
|  | ||||
| from django.db import models | ||||
| from django.templatetags.static import static | ||||
| from django.urls import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
| @ -49,7 +48,7 @@ class OAuthSource(Source): | ||||
|     consumer_secret = models.TextField() | ||||
|  | ||||
|     @property | ||||
|     def type(self) -> "SourceType": | ||||
|     def type(self) -> Type["SourceType"]: | ||||
|         """Return the provider instance for this source""" | ||||
|         from authentik.sources.oauth.types.manager import MANAGER | ||||
|  | ||||
| @ -67,6 +66,7 @@ class OAuthSource(Source): | ||||
|  | ||||
|     @property | ||||
|     def ui_login_button(self) -> UILoginButton: | ||||
|         provider_type = self.type | ||||
|         return UILoginButton( | ||||
|             challenge=RedirectChallenge( | ||||
|                 instance={ | ||||
| @ -77,7 +77,7 @@ class OAuthSource(Source): | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             icon_url=static(f"authentik/sources/{self.provider_type}.svg"), | ||||
|             icon_url=provider_type().icon_url(), | ||||
|             name=self.name, | ||||
|         ) | ||||
|  | ||||
| @ -173,6 +173,16 @@ class OpenIDConnectOAuthSource(OAuthSource): | ||||
|         verbose_name_plural = _("OpenID OAuth Sources") | ||||
|  | ||||
|  | ||||
| class AppleOAuthSource(OAuthSource): | ||||
|     """Login using a apple.com.""" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         abstract = True | ||||
|         verbose_name = _("Apple OAuth Source") | ||||
|         verbose_name_plural = _("Apple OAuth Sources") | ||||
|  | ||||
|  | ||||
| class UserOAuthSourceConnection(UserSourceConnection): | ||||
|     """Authorized remote OAuth provider.""" | ||||
|  | ||||
|  | ||||
							
								
								
									
										102
									
								
								authentik/sources/oauth/types/apple.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								authentik/sources/oauth/types/apple.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| """Apple OAuth Views""" | ||||
| from base64 import b64decode | ||||
| from json import loads | ||||
| from time import time | ||||
| from typing import Any, Optional | ||||
|  | ||||
| from jwt import encode | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.sources.oauth.clients.oauth2 import OAuth2Client | ||||
| 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 | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class AppleOAuthClient(OAuth2Client): | ||||
|     """Apple OAuth2 client""" | ||||
|  | ||||
|     def get_client_id(self) -> str: | ||||
|         parts = self.source.consumer_key.split(";") | ||||
|         if len(parts) < 3: | ||||
|             return self.source.consumer_key | ||||
|         return parts[0] | ||||
|  | ||||
|     def get_client_secret(self) -> str: | ||||
|         now = time() | ||||
|         parts = self.source.consumer_key.split(";") | ||||
|         if len(parts) < 3: | ||||
|             raise ValueError( | ||||
|                 ( | ||||
|                     "Apple Source client_id should be formatted like " | ||||
|                     "services_id_identifier;apple_team_id;key_id" | ||||
|                 ) | ||||
|             ) | ||||
|         LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2]) | ||||
|         payload = { | ||||
|             "iss": parts[1], | ||||
|             "iat": now, | ||||
|             "exp": now + 86400 * 180, | ||||
|             "aud": "https://appleid.apple.com", | ||||
|             "sub": self.source.consumer_key, | ||||
|         } | ||||
|         # pyright: reportGeneralTypeIssues=false | ||||
|         jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2]}) | ||||
|         LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt) | ||||
|         return jwt | ||||
|  | ||||
|     def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]: | ||||
|         id_token = token.get("id_token") | ||||
|         _, raw_payload, _ = id_token.split(".") | ||||
|         payload = loads(b64decode(raw_payload.encode().decode())) | ||||
|         return payload | ||||
|  | ||||
|  | ||||
| class AppleOAuthRedirect(OAuthRedirect): | ||||
|     """Apple OAuth2 Redirect""" | ||||
|  | ||||
|     client_class = AppleOAuthClient | ||||
|  | ||||
|     def get_additional_parameters(self, source):  # pragma: no cover | ||||
|         return { | ||||
|             "scope": "name email", | ||||
|             "response_mode": "form_post", | ||||
|         } | ||||
|  | ||||
|  | ||||
| class AppleOAuth2Callback(OAuthCallback): | ||||
|     """Apple OAuth2 Callback""" | ||||
|  | ||||
|     client_class = AppleOAuthClient | ||||
|  | ||||
|     def get_user_id(self, info: dict[str, Any]) -> Optional[str]: | ||||
|         return info["sub"] | ||||
|  | ||||
|     def get_user_enroll_context( | ||||
|         self, | ||||
|         info: dict[str, Any], | ||||
|     ) -> dict[str, Any]: | ||||
|         print(info) | ||||
|         return { | ||||
|             "email": info.get("email"), | ||||
|             "name": info.get("name"), | ||||
|         } | ||||
|  | ||||
|  | ||||
| @MANAGER.type() | ||||
| class AppleType(SourceType): | ||||
|     """Apple Type definition""" | ||||
|  | ||||
|     callback_view = AppleOAuth2Callback | ||||
|     redirect_view = AppleOAuthRedirect | ||||
|     name = "Apple" | ||||
|     slug = "apple" | ||||
|  | ||||
|     authorization_url = "https://appleid.apple.com/auth/authorize" | ||||
|     access_token_url = "https://appleid.apple.com/auth/token"  # nosec | ||||
|     profile_url = "" | ||||
|  | ||||
|     def icon_url(self) -> str: | ||||
|         return "https://appleid.cdn-apple.com/appleid/button/logo" | ||||
| @ -1,7 +1,8 @@ | ||||
| """Source type manager""" | ||||
| from enum import Enum | ||||
| from typing import Callable, Optional | ||||
| from typing import Callable, Optional, Type | ||||
|  | ||||
| from django.templatetags.static import static | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.sources.oauth.views.callback import OAuthCallback | ||||
| @ -22,8 +23,8 @@ class SourceType: | ||||
|  | ||||
|     callback_view = OAuthCallback | ||||
|     redirect_view = OAuthRedirect | ||||
|     name: str | ||||
|     slug: str | ||||
|     name: str = "default" | ||||
|     slug: str = "default" | ||||
|  | ||||
|     urls_customizable = False | ||||
|  | ||||
| @ -32,12 +33,16 @@ class SourceType: | ||||
|     access_token_url: Optional[str] = None | ||||
|     profile_url: Optional[str] = None | ||||
|  | ||||
|     def icon_url(self) -> str: | ||||
|         """Get Icon URL for login""" | ||||
|         return static(f"authentik/sources/{self.slug}.svg") | ||||
|  | ||||
|  | ||||
| class SourceTypeManager: | ||||
|     """Manager to hold all Source types.""" | ||||
|  | ||||
|     def __init__(self) -> None: | ||||
|         self.__sources: list[SourceType] = [] | ||||
|         self.__sources: list[Type[SourceType]] = [] | ||||
|  | ||||
|     def type(self): | ||||
|         """Class decorator to register classes inline.""" | ||||
| @ -56,14 +61,14 @@ class SourceTypeManager: | ||||
|         """Get list of tuples of all registered names""" | ||||
|         return [(x.slug, x.name) for x in self.__sources] | ||||
|  | ||||
|     def find_type(self, type_name: str) -> SourceType: | ||||
|     def find_type(self, type_name: str) -> Type[SourceType]: | ||||
|         """Find type based on source""" | ||||
|         found_type = None | ||||
|         for src_type in self.__sources: | ||||
|             if src_type.slug == type_name: | ||||
|                 return src_type | ||||
|         if not found_type: | ||||
|             found_type = SourceType() | ||||
|             found_type = SourceType | ||||
|             LOGGER.warning( | ||||
|                 "no matching type found, using default", | ||||
|                 wanted=type_name, | ||||
|  | ||||
| @ -24,7 +24,7 @@ class OAuthCallback(OAuthClientMixin, View): | ||||
|     source: OAuthSource | ||||
|  | ||||
|     # pylint: disable=too-many-return-statements | ||||
|     def get(self, request: HttpRequest, *_, **kwargs) -> HttpResponse: | ||||
|     def dispatch(self, request: HttpRequest, *_, **kwargs) -> HttpResponse: | ||||
|         """View Get handler""" | ||||
|         slug = kwargs.get("source_slug", "") | ||||
|         try: | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| """Dispatch OAuth views to respective views""" | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.views import View | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
| @ -9,6 +11,7 @@ from authentik.sources.oauth.types.manager import MANAGER, RequestKind | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @method_decorator(csrf_exempt, name="dispatch") | ||||
| class DispatcherView(View): | ||||
|     """Dispatch OAuth Redirect/Callback views to their proper class based on URL parameters""" | ||||
|  | ||||
|  | ||||
| @ -119,7 +119,7 @@ class TestIdentificationStage(APITestCase): | ||||
|                             "to": "/source/oauth/login/test/", | ||||
|                             "type": ChallengeTypes.REDIRECT.value, | ||||
|                         }, | ||||
|                         "icon_url": "/static/authentik/sources/.svg", | ||||
|                         "icon_url": "/static/authentik/sources/default.svg", | ||||
|                         "name": "test", | ||||
|                     } | ||||
|                 ], | ||||
| @ -170,7 +170,7 @@ class TestIdentificationStage(APITestCase): | ||||
|                             "to": "/source/oauth/login/test/", | ||||
|                             "type": ChallengeTypes.REDIRECT.value, | ||||
|                         }, | ||||
|                         "icon_url": "/static/authentik/sources/.svg", | ||||
|                         "icon_url": "/static/authentik/sources/default.svg", | ||||
|                         "name": "test", | ||||
|                     } | ||||
|                 ], | ||||
| @ -226,7 +226,7 @@ class TestIdentificationStage(APITestCase): | ||||
|                 }, | ||||
|                 "sources": [ | ||||
|                     { | ||||
|                         "icon_url": "/static/authentik/sources/.svg", | ||||
|                         "icon_url": "/static/authentik/sources/default.svg", | ||||
|                         "name": "test", | ||||
|                         "challenge": { | ||||
|                             "component": "xak-flow-redirect", | ||||
| @ -280,7 +280,7 @@ class TestIdentificationStage(APITestCase): | ||||
|                             "to": "/source/oauth/login/test/", | ||||
|                             "type": ChallengeTypes.REDIRECT.value, | ||||
|                         }, | ||||
|                         "icon_url": "/static/authentik/sources/.svg", | ||||
|                         "icon_url": "/static/authentik/sources/default.svg", | ||||
|                         "name": "test", | ||||
|                     } | ||||
|                 ], | ||||
|  | ||||
| @ -8,7 +8,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2021-10-11 14:12+0000\n" | ||||
| "POT-Creation-Date: 2021-10-18 10:40+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @ -308,6 +308,10 @@ msgstr "" | ||||
| msgid "Notification Webhook Mappings" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/events/monitored_tasks.py:122 | ||||
| msgid "Task has not been run yet." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/flows/api/flows.py:350 | ||||
| #, python-format | ||||
| msgid "Flow not applicable to current user/request: %(messages)s" | ||||
| @ -385,33 +389,33 @@ msgstr "" | ||||
| msgid "Invalid kubeconfig" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:164 | ||||
| #: authentik/outposts/models.py:167 | ||||
| msgid "Outpost Service-Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:165 | ||||
| #: authentik/outposts/models.py:168 | ||||
| msgid "Outpost Service-Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:201 | ||||
| #: authentik/outposts/models.py:204 | ||||
| msgid "" | ||||
| "Certificate/Key used for authentication. Can be left empty for no " | ||||
| "authentication." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:243 | ||||
| #: authentik/outposts/models.py:246 | ||||
| msgid "Docker Service-Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:244 | ||||
| #: authentik/outposts/models.py:247 | ||||
| msgid "Docker Service-Connections" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:290 | ||||
| #: authentik/outposts/models.py:293 | ||||
| msgid "Kubernetes Service-Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/outposts/models.py:291 | ||||
| #: authentik/outposts/models.py:294 | ||||
| msgid "Kubernetes Service-Connections" | ||||
| msgstr "" | ||||
|  | ||||
| @ -988,108 +992,116 @@ msgstr "" | ||||
| msgid "Password does not match Active Directory Complexity." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:25 | ||||
| #: authentik/sources/oauth/models.py:24 | ||||
| msgid "Request Token URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:27 | ||||
| #: authentik/sources/oauth/models.py:26 | ||||
| msgid "" | ||||
| "URL used to request the initial token. This URL is only required for OAuth 1." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:33 | ||||
| #: authentik/sources/oauth/models.py:32 | ||||
| msgid "Authorization URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:34 | ||||
| #: authentik/sources/oauth/models.py:33 | ||||
| msgid "URL the user is redirect to to conest the flow." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:39 | ||||
| #: authentik/sources/oauth/models.py:38 | ||||
| msgid "Access Token URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:40 | ||||
| #: authentik/sources/oauth/models.py:39 | ||||
| msgid "URL used by authentik to retrieve tokens." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:45 | ||||
| #: authentik/sources/oauth/models.py:44 | ||||
| msgid "Profile URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:46 | ||||
| #: authentik/sources/oauth/models.py:45 | ||||
| msgid "URL used by authentik to get user information." | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:102 | ||||
| #: authentik/sources/oauth/models.py:101 | ||||
| msgid "OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:103 | ||||
| #: authentik/sources/oauth/models.py:102 | ||||
| msgid "OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:112 | ||||
| #: authentik/sources/oauth/models.py:111 | ||||
| msgid "GitHub OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:113 | ||||
| #: authentik/sources/oauth/models.py:112 | ||||
| msgid "GitHub OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:122 | ||||
| #: authentik/sources/oauth/models.py:121 | ||||
| msgid "Twitter OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:123 | ||||
| #: authentik/sources/oauth/models.py:122 | ||||
| msgid "Twitter OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:132 | ||||
| #: authentik/sources/oauth/models.py:131 | ||||
| msgid "Facebook OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:133 | ||||
| #: authentik/sources/oauth/models.py:132 | ||||
| msgid "Facebook OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:142 | ||||
| #: authentik/sources/oauth/models.py:141 | ||||
| msgid "Discord OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:143 | ||||
| #: authentik/sources/oauth/models.py:142 | ||||
| msgid "Discord OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:152 | ||||
| #: authentik/sources/oauth/models.py:151 | ||||
| msgid "Google OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:153 | ||||
| #: authentik/sources/oauth/models.py:152 | ||||
| msgid "Google OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:162 | ||||
| #: authentik/sources/oauth/models.py:161 | ||||
| msgid "Azure AD OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:163 | ||||
| #: authentik/sources/oauth/models.py:162 | ||||
| msgid "Azure AD OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:172 | ||||
| #: authentik/sources/oauth/models.py:171 | ||||
| msgid "OpenID OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:173 | ||||
| #: authentik/sources/oauth/models.py:172 | ||||
| msgid "OpenID OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:188 | ||||
| #: authentik/sources/oauth/models.py:181 | ||||
| msgid "Apple OAuth Source" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:182 | ||||
| msgid "Apple OAuth Sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:197 | ||||
| msgid "User OAuth Source Connection" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/sources/oauth/models.py:189 | ||||
| #: authentik/sources/oauth/models.py:198 | ||||
| msgid "User OAuth Source Connections" | ||||
| msgstr "" | ||||
|  | ||||
| @ -1214,19 +1226,19 @@ msgstr "" | ||||
| msgid "Duo Devices" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_sms/models.py:97 | ||||
| #: authentik/stages/authenticator_sms/models.py:158 | ||||
| msgid "SMS Authenticator Setup Stage" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_sms/models.py:98 | ||||
| #: authentik/stages/authenticator_sms/models.py:159 | ||||
| msgid "SMS Authenticator Setup Stages" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_sms/models.py:116 | ||||
| #: authentik/stages/authenticator_sms/models.py:176 | ||||
| msgid "SMS Device" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_sms/models.py:117 | ||||
| #: authentik/stages/authenticator_sms/models.py:177 | ||||
| msgid "SMS Devices" | ||||
| msgstr "" | ||||
|  | ||||
| @ -1291,19 +1303,19 @@ msgstr "" | ||||
| msgid "Authenticator Validation Stages" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_webauthn/models.py:49 | ||||
| #: authentik/stages/authenticator_webauthn/models.py:51 | ||||
| msgid "WebAuthn Authenticator Setup Stage" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_webauthn/models.py:50 | ||||
| #: authentik/stages/authenticator_webauthn/models.py:52 | ||||
| msgid "WebAuthn Authenticator Setup Stages" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_webauthn/models.py:78 | ||||
| #: authentik/stages/authenticator_webauthn/models.py:85 | ||||
| msgid "WebAuthn Device" | ||||
| msgstr "" | ||||
|  | ||||
| #: authentik/stages/authenticator_webauthn/models.py:79 | ||||
| #: authentik/stages/authenticator_webauthn/models.py:86 | ||||
| msgid "WebAuthn Devices" | ||||
| msgstr "" | ||||
|  | ||||
|  | ||||
| @ -43,7 +43,7 @@ class OAUth1Type(SourceType): | ||||
|     urls_customizable = False | ||||
|  | ||||
|  | ||||
| SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type()) | ||||
| SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type) | ||||
|  | ||||
|  | ||||
| @skipUnless(platform.startswith("linux"), "requires local docker") | ||||
|  | ||||
| @ -252,7 +252,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | ||||
|                         ?writeOnly=${this.instance !== undefined} | ||||
|                         name="consumerSecret" | ||||
|                     > | ||||
|                         <input type="text" value="" class="pf-c-form-control" required /> | ||||
|                         <textarea class="pf-c-form-control"></textarea> | ||||
|                     </ak-form-element-horizontal> | ||||
|                 </div> | ||||
|             </ak-form-group> | ||||
|  | ||||
| @ -50,7 +50,7 @@ In authentik, create an application which uses this provider. Optionally apply a | ||||
|  | ||||
| ### Step 3 | ||||
|  | ||||
| Obtain your Metadata URL from Authentik. | ||||
| Obtain your Metadata URL from authentik. | ||||
|  | ||||
| 1. Click on the BookStack Provider | ||||
| 2. Click the Metadata Tab | ||||
| @ -69,7 +69,7 @@ Modify the following Example SAML config and paste incorporate into your `.env` | ||||
| AUTH_METHOD=saml2 | ||||
| # Set the display name to be shown on the login button. | ||||
| # (Login with <name>) | ||||
| SAML2_NAME=Authentik | ||||
| SAML2_NAME=authentik | ||||
| # Name of the attribute which provides the user's email address | ||||
| SAML2_EMAIL_ATTRIBUTE=email | ||||
| # Name of the attribute to use as an ID for the SAML user. | ||||
|  | ||||
| @ -21,7 +21,7 @@ The following placeholders will be used: | ||||
| - `port.company` is the FQDN of Portainer. | ||||
| - `authentik.company` is the FQDN of authentik. | ||||
|  | ||||
| ### Step 1 - Authentik | ||||
| ### Step 1 - authentik | ||||
|  | ||||
| In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings: | ||||
|  | ||||
| @ -57,7 +57,7 @@ Portainer by default shows commas between each item in the Scopes field.  Do **N | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Step 3 - Authentik | ||||
| ### Step 3 - authentik | ||||
|  | ||||
| In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings. | ||||
|  | ||||
|  | ||||
| @ -76,9 +76,9 @@ auth: | ||||
|         # The auth url to send users to if they want to authenticate using OpenID Connect. | ||||
|         authurl: https://authentik.company/application/o/vikunja/ | ||||
|         # The client ID used to authenticate Vikunja at the OpenID Connect provider. | ||||
|         clientid: THIS IS THE CLIENT ID YOU COPIED FROM STEP 1 in Authentik | ||||
|         clientid: THIS IS THE CLIENT ID YOU COPIED FROM STEP 1 in authentik | ||||
|         # The client secret used to authenticate Vikunja at the OpenID Connect provider. | ||||
|         clientsecret: THIS IS THE CLIENT SECRET YOU COPIED FROM STEP 1 in Authentik | ||||
|         clientsecret: THIS IS THE CLIENT SECRET YOU COPIED FROM STEP 1 in authentik | ||||
| ``` | ||||
|  | ||||
| :::note | ||||
|  | ||||
| @ -39,7 +39,7 @@ import TabItem from '@theme/TabItem'; | ||||
|     {label: 'Standalone', value: 'standalone'}, | ||||
|   ]}> | ||||
|   <TabItem value="docker"> | ||||
| If your Wekan is running in docker, add the following environment variables for Authentik | ||||
| If your Wekan is running in docker, add the following environment variables for authentik | ||||
|  | ||||
| ```yaml | ||||
| environment: | ||||
| @ -62,7 +62,7 @@ environment: | ||||
| edit `.env` and add the following: | ||||
|  | ||||
| ```ini | ||||
|      # Authentik OAUTH Config | ||||
|      # authentik OAUTH Config | ||||
|       OAUTH2_ENABLED='true' | ||||
|       OAUTH2_LOGIN_STYLE='redirect' | ||||
|       OAUTH2_CLIENT_ID='<Client ID from above>' | ||||
|  | ||||
| @ -21,7 +21,7 @@ The following placeholders will be used: | ||||
| - `wp.company` is the FQDN of Wordpress. | ||||
| - `authentik.company` is the FQDN of authentik. | ||||
|  | ||||
| ### Step 1 - Authentik | ||||
| ### Step 1 - authentik | ||||
|  | ||||
| In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings: | ||||
|  | ||||
| @ -63,7 +63,7 @@ Only settings that have been modified from default have been listed. | ||||
| Review each setting and choose the ones that you require for your installation.  Examples of popular settings are _Link Existing Users_, _Create user if does not exist_, and _Enforce Privacy_ | ||||
| ::: | ||||
|  | ||||
| ### Step 3 - Authentik | ||||
| ### Step 3 - authentik | ||||
|  | ||||
| In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings. | ||||
|  | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/app_id.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/app_id.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 62 KiB | 
							
								
								
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/app_service_config.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/app_service_config.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 120 KiB | 
							
								
								
									
										67
									
								
								website/docs/integrations/sources/apple/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								website/docs/integrations/sources/apple/index.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| --- | ||||
| title: Apple | ||||
| --- | ||||
|  | ||||
| Allows users to authenticate using their Apple ID. | ||||
|  | ||||
| ## Preparation | ||||
|  | ||||
| :::warning | ||||
| An Apple developer account is required for this. | ||||
| ::: | ||||
|  | ||||
| The following placeholders will be used: | ||||
|  | ||||
| - `authentik.company` is the FQDN of the authentik install. | ||||
|  | ||||
| ## Apple | ||||
|  | ||||
| 1. Log into your Apple developer account, and navigate to **Certificates, IDs & Profiles**, then click **Identifiers** in the sidebar. | ||||
| 2. Register a new Identifier with the type of **App IDs**, and the subtype **App**. | ||||
| 3. Choose a name that users will recognise for the **Description** field. | ||||
| 4. For your bundle ID, use the reverse domain of authentik, in this case `company.authentik`. | ||||
| 5. Scroll down the list of capabilities, and check the box next to **Sign In with Apple**. | ||||
| 6. At the top, click **Continue** and **Register**. | ||||
|  | ||||
|  | ||||
|  | ||||
| 7. Register another new Identifier with the type of **Services IDs**. | ||||
| 8. Again, choose the same name as above for your **Description** field. | ||||
| 9. Use the same identifier as above, but add a suffix like `signin` or `oauth`, as identifiers are unique. | ||||
| 10. At the top, click **Continue** and **Register**. | ||||
|  | ||||
|  | ||||
|  | ||||
| 11. Once back at the overview list, click on the just-created Identifier. | ||||
| 12. Enable the checkbox next to **Sign In with Apple**, and click **Configure** | ||||
| 13. Under domains, enter `authentik.company`. | ||||
| 14. Under **Return URLs**, enter `https://authentik.company/source/oauth/callback/apple/`. | ||||
|  | ||||
|  | ||||
|  | ||||
| 15. Click on **Keys** in the sidebar. Register a new Key with any name, and select **Sign in with Apple**. | ||||
| 16. Click on **Configure**, and select the App ID you've created above. | ||||
| 17. At the top, click **Save**, **Continue** and **Register**. | ||||
| 18. Download the Key file and note the **Key ID**. | ||||
|  | ||||
|  | ||||
|  | ||||
| 19. Note the Team ID, visible at the top of the page. | ||||
|  | ||||
| ## authentik | ||||
|  | ||||
| 20. Under _Resources -> Sources_ Click **Create Apple OAuth Source** | ||||
|  | ||||
| 21. **Name**: `Apple` | ||||
| 22. **Slug**: `apple` | ||||
| 23. **Consumer Key:** The identifier from step 9, then `;`, then your Team ID from step 19, then `;`, then the Key ID from step 18. | ||||
|  | ||||
|     Example: `io.goauthentik.dev-local;JQNH45HN7V;XFBNJ82BV6` | ||||
|  | ||||
| 24. **Consumer Secret:** Paste the contents of the keyfile you've downloaded | ||||
|  | ||||
| Save, and you now have Apple as a source. | ||||
|  | ||||
| :::note | ||||
| For more details on how-to have the new source display on the Login Page see the Sources page. | ||||
| ::: | ||||
							
								
								
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/key.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/key.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 71 KiB | 
							
								
								
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/service_id.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								website/docs/integrations/sources/apple/service_id.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 53 KiB | 
| @ -33,7 +33,7 @@ Here is an example of a completed OAuth2 screen for Discord. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Authentik | ||||
| ## authentik | ||||
|  | ||||
| 8. Under _Resources -> Sources_ Click **Create Discord OAuth Source** | ||||
|  | ||||
| @ -43,7 +43,7 @@ Here is an example of a completed OAuth2 screen for Discord. | ||||
| 12. **Consumer Secret:** Client Secret from step 5 | ||||
| 13. **Provider type:** Discord | ||||
|  | ||||
| Here is an exmple of a complete Authentik Discord OAuth Source | ||||
| Here is an example of a complete authentik Discord OAuth Source | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -17,7 +17,7 @@ The following placeholders will be used: | ||||
|  | ||||
|  | ||||
|  | ||||
| 2. **Application Name:** Choose a name users will recognize ie: Authentik | ||||
| 2. **Application Name:** Choose a name users will recognize ie: authentik | ||||
| 3. **Homepage URL**:: www.my.company | ||||
| 4. **Authorization callback URL**: https://authentik.company/source/oauth/callback/github | ||||
| 5. Click **Register Application** | ||||
| @ -29,7 +29,7 @@ Example screenshot | ||||
| 6. Copy the **Client ID** and _save it for later_ | ||||
| 7. Click **Generate a new client secret** and _save it for later_  You will not be able to see the secret again, so be sure to copy it now. | ||||
|  | ||||
| ## Authentik | ||||
| ## authentik | ||||
|  | ||||
| 8. Under _Resources -> Sources_ Click **Create Github OAuth Source** | ||||
|  | ||||
| @ -49,7 +49,7 @@ As of June 20 2021 these URLS are correct. Here is the Github reference URL http | ||||
| 15. **Access token URL:** `https://github.com/login/oauth/access_token` | ||||
| 16. **Profile URL:** `https://api.github.com/user` | ||||
|  | ||||
| Here is an exmple of a complete Authentik Github OAuth Source | ||||
| Here is an example of a complete authentik Github OAuth Source | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -62,7 +62,7 @@ _I'm only going to list the mandatory/important fields to complete._ | ||||
| 24. Click **Create** | ||||
| 25. Copy and store _Your Client ID_ and _Your Client Secret_ for later | ||||
|  | ||||
| ## Authentik | ||||
| ## authentik | ||||
|  | ||||
| 26. Under _Resources -> Sources_ Click **Create Google OAuth Source** | ||||
|  | ||||
| @ -72,7 +72,7 @@ _I'm only going to list the mandatory/important fields to complete._ | ||||
| 30. **Consumer Secret:** Your Client Secret from step 25 | ||||
| 31. **Provider Type:** Google | ||||
|  | ||||
| Here is an exmple of a complete Authentik Google OAuth Source | ||||
| Here is an example of a complete authentik Google OAuth Source | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -8,7 +8,7 @@ Allows users to authenticate using their Plex credentials | ||||
|  | ||||
| None | ||||
|  | ||||
| ## Authentik -> Sources | ||||
| ## authentik -> Sources | ||||
|  | ||||
| Add _Plex_ as a _source_ | ||||
|  | ||||
|  | ||||
| @ -73,7 +73,7 @@ server { | ||||
|         # authentik-specific config | ||||
|         auth_request        /akprox/auth/nginx; | ||||
|         error_page          401 = @akprox_signin; | ||||
|         # For domain level, use the below error_page to redirect to your Authentik server with the full redirect path | ||||
|         # For domain level, use the below error_page to redirect to your authentik server with the full redirect path | ||||
|         # error_page          401 =302 https://authentik.company/akprox/start?rd=$scheme://$http_host$request_uri; | ||||
|  | ||||
|         # translate headers from the outposts back to the actual upstream | ||||
|  | ||||
| @ -69,7 +69,7 @@ error_reporting: | ||||
|  | ||||
| ### Upgrading | ||||
|  | ||||
| This upgrade only applies if you are upgrading from a running 0.9 instance. Authentik detects this on startup, and automatically executes this upgrade. | ||||
| This upgrade only applies if you are upgrading from a running 0.9 instance. authentik detects this on startup, and automatically executes this upgrade. | ||||
|  | ||||
| Because this upgrade brings the new OAuth2 Provider, the old providers will be lost in the process. Make sure to take note of the providers you want to bring over. | ||||
|  | ||||
|  | ||||
| @ -64,6 +64,7 @@ module.exports = { | ||||
|                     label: "as Source", | ||||
|                     items: [ | ||||
|                         "integrations/sources/index", | ||||
|                         "integrations/sources/apple/index", | ||||
|                         "integrations/sources/active-directory/index", | ||||
|                         "integrations/sources/discord/index", | ||||
|                         "integrations/sources/github/index", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L