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:
parent
2c06eed8e7
commit
922fc9b8d5
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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
|
|||
|
||||
![](./port1.png)
|
||||
|
||||
### 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.
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
|
@ -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**.
|
||||
|
||||
![](app_id.png)
|
||||
|
||||
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**.
|
||||
|
||||
![](service_id.png)
|
||||
|
||||
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/`.
|
||||
|
||||
![](app_service_config.png)
|
||||
|
||||
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**.
|
||||
|
||||
![](key.png)
|
||||
|
||||
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.
|
||||
:::
|
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
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.
|
|||
|
||||
![Example Screen](discord4.png)
|
||||
|
||||
## 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
|
||||
|
||||
![Example Screen](discord5.png)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ The following placeholders will be used:
|
|||
|
||||
![Register OAuth App](githubdeveloper1.png)
|
||||
|
||||
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
|
||||
|
||||
![Example Screen](githubexample2.png)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
![Example Screen](authentiksource.png)
|
||||
|
||||
|
|
|
@ -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