sources/oauth: add patreon type (#5452)
* Models Update to include Patreon as Social Sign On Signed-off-by: DerGardine <julian.burgschweiger@gmail.com> Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests, use vanity as username Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: DerGardine <julian.burgschweiger@gmail.com> Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
0ae53b1ce8
commit
a2994218e4
|
@ -12,12 +12,13 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
|||
"authentik.sources.oauth.types.facebook",
|
||||
"authentik.sources.oauth.types.github",
|
||||
"authentik.sources.oauth.types.google",
|
||||
"authentik.sources.oauth.types.mailcow",
|
||||
"authentik.sources.oauth.types.oidc",
|
||||
"authentik.sources.oauth.types.okta",
|
||||
"authentik.sources.oauth.types.patreon",
|
||||
"authentik.sources.oauth.types.reddit",
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
"authentik.sources.oauth.types.mailcow",
|
||||
"authentik.sources.oauth.types.twitch",
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -163,6 +163,15 @@ class DiscordOAuthSource(OAuthSource):
|
|||
verbose_name_plural = _("Discord OAuth Sources")
|
||||
|
||||
|
||||
class PatreonOAuthSource(OAuthSource):
|
||||
"""Social Login using Patreon."""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _("Patreon OAuth Source")
|
||||
verbose_name_plural = _("Patreon OAuth Sources")
|
||||
|
||||
|
||||
class GoogleOAuthSource(OAuthSource):
|
||||
"""Social Login using Google or Google Workspace (GSuite)."""
|
||||
|
||||
|
|
67
authentik/sources/oauth/tests/test_type_patreon.py
Normal file
67
authentik/sources/oauth/tests/test_type_patreon.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""Patreon Type tests"""
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.patreon import PatreonOAuthCallback
|
||||
|
||||
PATREON_USER = {
|
||||
"data": {
|
||||
"attributes": {
|
||||
"about": None,
|
||||
"created": "2017-10-20T21:36:23+00:00",
|
||||
"discord_id": None,
|
||||
"email": "corgi@example.com",
|
||||
"facebook": None,
|
||||
"facebook_id": None,
|
||||
"first_name": "Corgi",
|
||||
"full_name": "Corgi The Dev",
|
||||
"gender": 0,
|
||||
"has_password": True,
|
||||
"image_url": "https://c8.patreon.com/2/400/0000000",
|
||||
"is_deleted": False,
|
||||
"is_email_verified": False,
|
||||
"is_nuked": False,
|
||||
"is_suspended": False,
|
||||
"last_name": "The Dev",
|
||||
"social_connections": {
|
||||
"deviantart": None,
|
||||
"discord": None,
|
||||
"facebook": None,
|
||||
"reddit": None,
|
||||
"spotify": None,
|
||||
"twitch": None,
|
||||
"twitter": None,
|
||||
"youtube": None,
|
||||
},
|
||||
"thumb_url": "https://c8.patreon.com/2/100/0000000",
|
||||
"twitch": None,
|
||||
"twitter": None,
|
||||
"url": "https://www.patreon.com/corgithedev",
|
||||
"vanity": "corgithedev",
|
||||
"youtube": None,
|
||||
},
|
||||
"id": "0000000",
|
||||
"relationships": {"pledges": {"data": []}},
|
||||
"type": "user",
|
||||
},
|
||||
"links": {"self": "https://www.patreon.com/api/user/0000000"},
|
||||
}
|
||||
|
||||
|
||||
class TestTypePatreon(TestCase):
|
||||
"""OAuth Source tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.source = OAuthSource.objects.create(
|
||||
name="test",
|
||||
slug="test",
|
||||
provider_type="Patreon",
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_enroll_context(self):
|
||||
"""Test Patreon Enrollment context"""
|
||||
ak_context = PatreonOAuthCallback().get_user_enroll_context(PATREON_USER)
|
||||
self.assertEqual(ak_context["username"], PATREON_USER["data"]["attributes"]["vanity"])
|
||||
self.assertEqual(ak_context["email"], PATREON_USER["data"]["attributes"]["email"])
|
||||
self.assertEqual(ak_context["name"], PATREON_USER["data"]["attributes"]["full_name"])
|
50
authentik/sources/oauth/types/patreon.py
Normal file
50
authentik/sources/oauth/types/patreon.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
"""Patreon OAuth Views"""
|
||||
from typing import Any
|
||||
|
||||
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import SourceType, registry
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
class PatreonOAuthRedirect(OAuthRedirect):
|
||||
"""Patreon OAuth2 Redirect"""
|
||||
|
||||
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
|
||||
return {
|
||||
"scope": ["openid", "email", "profile"],
|
||||
}
|
||||
|
||||
|
||||
class PatreonOAuthCallback(OAuthCallback):
|
||||
"""Patreon OAuth2 Callback"""
|
||||
|
||||
client_class: UserprofileHeaderAuthClient
|
||||
|
||||
def get_user_id(self, info: dict[str, str]) -> str:
|
||||
return info.get("data", {}).get("id")
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"username": info.get("data", {}).get("attributes", {}).get("vanity"),
|
||||
"email": info.get("data", {}).get("attributes", {}).get("email"),
|
||||
"name": info.get("data", {}).get("attributes", {}).get("full_name"),
|
||||
}
|
||||
|
||||
|
||||
@registry.register()
|
||||
class PatreonType(SourceType):
|
||||
"""OpenIDConnect Type definition"""
|
||||
|
||||
callback_view = PatreonOAuthCallback
|
||||
redirect_view = PatreonOAuthRedirect
|
||||
name = "Patreon"
|
||||
slug = "patreon"
|
||||
|
||||
authorization_url = "https://www.patreon.com/oauth2/authorize"
|
||||
access_token_url = "https://www.patreon.com/api/oauth2/token" # nosec
|
||||
profile_url = "https://www.patreon.com/api/oauth2/api/current_user"
|
6
web/authentik/sources/patreon.svg
Normal file
6
web/authentik/sources/patreon.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="247px" viewBox="0 0 256 247" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M45.1355837,0 L45.1355837,246.35001 L0,246.35001 L0,0 L45.1355837,0 Z M163.657111,0 C214.65668,0 256,41.3433196 256,92.3428889 C256,143.342458 214.65668,184.685778 163.657111,184.685778 C112.657542,184.685778 71.3142222,143.342458 71.3142222,92.3428889 C71.3142222,41.3433196 112.657542,0 163.657111,0 Z" fill="#FF424D"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 587 B |
Reference in a new issue