sources/oauth: migrate twitter to oauth2 (#2893)
This commit is contained in:
parent
538c2ca4d3
commit
75b0fb3393
|
@ -11,6 +11,7 @@ from structlog.stdlib import get_logger
|
|||
from authentik.sources.oauth.clients.base import BaseOAuthClient
|
||||
|
||||
LOGGER = get_logger()
|
||||
SESSION_OAUTH_PKCE = "oauth_pkce"
|
||||
|
||||
|
||||
class OAuth2Client(BaseOAuthClient):
|
||||
|
@ -69,6 +70,8 @@ class OAuth2Client(BaseOAuthClient):
|
|||
"code": code,
|
||||
"grant_type": "authorization_code",
|
||||
}
|
||||
if SESSION_OAUTH_PKCE in self.request.session:
|
||||
args["code_verifier"] = self.request.session[SESSION_OAUTH_PKCE]
|
||||
try:
|
||||
access_token_url = self.source.type.access_token_url or ""
|
||||
if self.source.type.urls_customizable and self.source.access_token_url:
|
||||
|
|
|
@ -4,88 +4,8 @@ from django.test import TestCase
|
|||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
|
||||
|
||||
# https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/ \
|
||||
# api-reference/get-account-verify_credentials
|
||||
TWITTER_USER = {
|
||||
"contributors_enabled": True,
|
||||
"created_at": "Sat May 09 17:58:22 +0000 2009",
|
||||
"default_profile": False,
|
||||
"default_profile_image": False,
|
||||
"description": "I taught your phone that thing you like.",
|
||||
"favourites_count": 588,
|
||||
"follow_request_sent": None,
|
||||
"followers_count": 10625,
|
||||
"following": None,
|
||||
"friends_count": 1181,
|
||||
"geo_enabled": True,
|
||||
"id": 38895958,
|
||||
"id_str": "38895958",
|
||||
"is_translator": False,
|
||||
"lang": "en",
|
||||
"listed_count": 190,
|
||||
"location": "San Francisco",
|
||||
"name": "Sean Cook",
|
||||
"notifications": None,
|
||||
"profile_background_color": "1A1B1F",
|
||||
"profile_background_image_url": "",
|
||||
"profile_background_image_url_https": "",
|
||||
"profile_background_tile": True,
|
||||
"profile_image_url": "",
|
||||
"profile_image_url_https": "",
|
||||
"profile_link_color": "2FC2EF",
|
||||
"profile_sidebar_border_color": "181A1E",
|
||||
"profile_sidebar_fill_color": "252429",
|
||||
"profile_text_color": "666666",
|
||||
"profile_use_background_image": True,
|
||||
"protected": False,
|
||||
"screen_name": "theSeanCook",
|
||||
"show_all_inline_media": True,
|
||||
"status": {
|
||||
"contributors": None,
|
||||
"coordinates": {"coordinates": [-122.45037293, 37.76484123], "type": "Point"},
|
||||
"created_at": "Tue Aug 28 05:44:24 +0000 2012",
|
||||
"favorited": False,
|
||||
"geo": {"coordinates": [37.76484123, -122.45037293], "type": "Point"},
|
||||
"id": 240323931419062272,
|
||||
"id_str": "240323931419062272",
|
||||
"in_reply_to_screen_name": "messl",
|
||||
"in_reply_to_status_id": 240316959173009410,
|
||||
"in_reply_to_status_id_str": "240316959173009410",
|
||||
"in_reply_to_user_id": 18707866,
|
||||
"in_reply_to_user_id_str": "18707866",
|
||||
"place": {
|
||||
"attributes": {},
|
||||
"bounding_box": {
|
||||
"coordinates": [
|
||||
[
|
||||
[-122.45778216, 37.75932999],
|
||||
[-122.44248216, 37.75932999],
|
||||
[-122.44248216, 37.76752899],
|
||||
[-122.45778216, 37.76752899],
|
||||
]
|
||||
],
|
||||
"type": "Polygon",
|
||||
},
|
||||
"country": "United States",
|
||||
"country_code": "US",
|
||||
"full_name": "Ashbury Heights, San Francisco",
|
||||
"id": "866269c983527d5a",
|
||||
"name": "Ashbury Heights",
|
||||
"place_type": "neighborhood",
|
||||
"url": "http://api.twitter.com/1/geo/id/866269c983527d5a.json",
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"retweeted": False,
|
||||
"source": "Twitter for iPhone",
|
||||
"text": "@messl congrats! So happy for all 3 of you.",
|
||||
"truncated": False,
|
||||
},
|
||||
"statuses_count": 2609,
|
||||
"time_zone": "Pacific Time (US & Canada)",
|
||||
"url": None,
|
||||
"utc_offset": -28800,
|
||||
"verified": False,
|
||||
}
|
||||
# https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
|
||||
TWITTER_USER = {"data": {"id": "2244994945", "name": "TwitterDev", "username": "Twitter Dev"}}
|
||||
|
||||
|
||||
class TestTypeGitHub(TestCase):
|
||||
|
@ -104,6 +24,6 @@ class TestTypeGitHub(TestCase):
|
|||
def test_enroll_context(self):
|
||||
"""Test Twitter Enrollment context"""
|
||||
ak_context = TwitterOAuthCallback().get_user_enroll_context(TWITTER_USER)
|
||||
self.assertEqual(ak_context["username"], TWITTER_USER["screen_name"])
|
||||
self.assertEqual(ak_context["email"], TWITTER_USER.get("email", None))
|
||||
self.assertEqual(ak_context["name"], TWITTER_USER["name"])
|
||||
self.assertEqual(ak_context["username"], TWITTER_USER["data"]["username"])
|
||||
self.assertEqual(ak_context["email"], None)
|
||||
self.assertEqual(ak_context["name"], TWITTER_USER["data"]["name"])
|
||||
|
|
|
@ -1,21 +1,46 @@
|
|||
"""Twitter OAuth Views"""
|
||||
from typing import Any
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.sources.oauth.clients.oauth2 import SESSION_OAUTH_PKCE
|
||||
from authentik.sources.oauth.types.azure_ad import AzureADClient
|
||||
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
|
||||
|
||||
|
||||
class TwitterOAuthRedirect(OAuthRedirect):
|
||||
"""Twitter OAuth2 Redirect"""
|
||||
|
||||
def get_additional_parameters(self, source): # pragma: no cover
|
||||
self.request.session[SESSION_OAUTH_PKCE] = generate_id()
|
||||
return {
|
||||
"scope": ["users.read", "tweet.read"],
|
||||
"code_challenge": self.request.session[SESSION_OAUTH_PKCE],
|
||||
"code_challenge_method": "plain",
|
||||
}
|
||||
|
||||
|
||||
class TwitterOAuthCallback(OAuthCallback):
|
||||
"""Twitter OAuth2 Callback"""
|
||||
|
||||
# Twitter has the same quirk as azure and throws an error if the access token
|
||||
# is set via query parameter, so we re-use the azure client
|
||||
# see https://github.com/goauthentik/authentik/issues/1910
|
||||
client_class = AzureADClient
|
||||
|
||||
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]:
|
||||
data = info.get("data", {})
|
||||
return {
|
||||
"username": info.get("screen_name"),
|
||||
"email": info.get("email", None),
|
||||
"name": info.get("name"),
|
||||
"username": data.get("username"),
|
||||
"email": None,
|
||||
"name": data.get("name"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,10 +49,10 @@ class TwitterType(SourceType):
|
|||
"""Twitter Type definition"""
|
||||
|
||||
callback_view = TwitterOAuthCallback
|
||||
redirect_view = TwitterOAuthRedirect
|
||||
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"
|
||||
authorization_url = "https://twitter.com/i/oauth2/authorize"
|
||||
access_token_url = "https://api.twitter.com/2/oauth2/token" # nosec
|
||||
profile_url = "https://api.twitter.com/2/users/me"
|
||||
|
|
|
@ -4,7 +4,6 @@ from sys import platform
|
|||
from time import sleep
|
||||
from typing import Any, Optional
|
||||
from unittest.case import skipUnless
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from docker.models.containers import Container
|
||||
from docker.types import Healthcheck
|
||||
|
@ -18,20 +17,38 @@ from authentik.core.models import User
|
|||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.manager import SourceType
|
||||
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
|
||||
from authentik.sources.oauth.types.manager import MANAGER, SourceType
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
|
||||
CONFIG_PATH = "/tmp/dex.yml" # nosec
|
||||
|
||||
|
||||
class OAUth1Type(SourceType):
|
||||
"""Twitter Type definition"""
|
||||
class OAUth1Callback(OAuthCallback):
|
||||
"""OAuth1 Callback with custom getters"""
|
||||
|
||||
callback_view = TwitterOAuthCallback
|
||||
name = "Twitter"
|
||||
slug = "twitter"
|
||||
def get_user_id(self, info: dict[str, str]) -> str:
|
||||
return info.get("id")
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"username": info.get("screen_name"),
|
||||
"email": info.get("email"),
|
||||
"name": info.get("name"),
|
||||
}
|
||||
|
||||
|
||||
@MANAGER.type()
|
||||
class OAUth1Type(SourceType):
|
||||
"""OAuth1 Type definition"""
|
||||
|
||||
callback_view = OAUth1Callback
|
||||
name = "OAuth1"
|
||||
slug = "oauth1"
|
||||
|
||||
request_token_url = "http://localhost:5000/oauth/request_token" # nosec
|
||||
access_token_url = "http://localhost:5000/oauth/access_token" # nosec
|
||||
|
@ -40,9 +57,6 @@ class OAUth1Type(SourceType):
|
|||
urls_customizable = False
|
||||
|
||||
|
||||
SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type)
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
class TestSourceOAuth2(SeleniumTestCase):
|
||||
"""test OAuth Source flow"""
|
||||
|
@ -256,7 +270,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
slug=self.source_slug,
|
||||
authentication_flow=authentication_flow,
|
||||
enrollment_flow=enrollment_flow,
|
||||
provider_type="twitter",
|
||||
provider_type="oauth1",
|
||||
consumer_key=self.client_id,
|
||||
consumer_secret=self.client_secret,
|
||||
)
|
||||
|
@ -269,10 +283,6 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@patch(
|
||||
"authentik.sources.oauth.types.manager.SourceTypeManager.find_type",
|
||||
SOURCE_TYPE_MOCK,
|
||||
)
|
||||
@object_manager
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
|
|
|
@ -30,7 +30,7 @@ from authentik.core.models import User
|
|||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.managed.manager import ObjectManager
|
||||
|
||||
RETRIES = int(environ.get("RETRIES", "5"))
|
||||
RETRIES = int(environ.get("RETRIES", "3"))
|
||||
|
||||
|
||||
def get_docker_tag() -> str:
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,10 @@ slug: "2022.5"
|
|||
|
||||
## Breaking changes
|
||||
|
||||
- Twitter Source has been migrated to OAuth2
|
||||
|
||||
This requires some reconfiguration on both Twitter's and authentik's side. Check out the new Twitter integration docs [here](../../integrations/sources/twitter/)
|
||||
|
||||
## New features
|
||||
|
||||
- LDAP Outpost cached binding
|
||||
|
|
|
@ -50,7 +50,7 @@ The following placeholders will be used:
|
|||
|
||||
## authentik
|
||||
|
||||
20. Under _Resources -> Sources_ Click **Create Apple OAuth Source**
|
||||
20. Under _Directory -> Federation & Social login_ Click **Create Apple OAuth Source**
|
||||
|
||||
21. **Name**: `Apple`
|
||||
22. **Slug**: `apple`
|
||||
|
|
|
@ -30,21 +30,20 @@ The following placeholders will be used:
|
|||
|
||||
Here is an example of a completed OAuth2 screen for Discord.
|
||||
|
||||
![Example Screen](discord4.png)
|
||||
![](discord4.png)
|
||||
|
||||
## authentik
|
||||
|
||||
8. Under _Resources -> Sources_ Click **Create Discord OAuth Source**
|
||||
8. Under _Directory -> Federation & Social login_ Click **Create Discord OAuth Source**
|
||||
|
||||
9. **Name:** Choose a name (For the example I used Discord)
|
||||
10. **Slug:** discord (You can choose a different slug, if you do you will need to update the Discord redirect URLand point it to the correct slug.)
|
||||
11. **Consumer Key:** Client ID from step 4
|
||||
12. **Consumer Secret:** Client Secret from step 5
|
||||
13. **Provider type:** Discord
|
||||
|
||||
Here is an example of a complete authentik Discord OAuth Source
|
||||
|
||||
![Example Screen](discord5.png)
|
||||
![](discord5.png)
|
||||
|
||||
Save, and you now have Discord as a source.
|
||||
|
||||
|
|
|
@ -24,24 +24,23 @@ The following placeholders will be used:
|
|||
|
||||
Example screenshot
|
||||
|
||||
![Example Screen](githubdeveloperexample.png)
|
||||
![](githubdeveloperexample.png)
|
||||
|
||||
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
|
||||
|
||||
8. Under _Resources -> Sources_ Click **Create Github OAuth Source**
|
||||
8. Under _Directory -> Federation & Social login_ Click **Create Github OAuth Source**
|
||||
|
||||
9. **Name**: Choose a name (For the example I use Github)
|
||||
10. **Slug**: github (If you choose a different slug the URLs will need to be updated to reflect the change)
|
||||
11. **Consumer Key:** Client ID from step 6
|
||||
12. **Consumer Secret:** Client Secret from step 7
|
||||
13. **Provider Type:** Github
|
||||
|
||||
Here is an example of a complete authentik Github OAuth Source
|
||||
|
||||
![Example Screen](githubexample2.png)
|
||||
![](githubexample2.png)
|
||||
|
||||
Save, and you now have Github as a source.
|
||||
|
||||
|
|
|
@ -17,23 +17,23 @@ You will need to create a new project, and OAuth credentials in the Google Devel
|
|||
1. Visit https://console.developers.google.com/ to create a new project
|
||||
2. Create a New project.
|
||||
|
||||
![Example Screen](googledeveloper1.png)
|
||||
![](googledeveloper1.png)
|
||||
|
||||
3. **Project Name**: Choose a name
|
||||
4. **Organization**: Leave as default if unsure
|
||||
5. **Location**: Leave as default if unsure
|
||||
|
||||
![Example Screen](googledeveloper2.png)
|
||||
![](googledeveloper2.png)
|
||||
|
||||
6. Click **Create**
|
||||
7. Choose your project from the drop down at the top
|
||||
8. Click the **Credentials** menu item on the left. It looks like a key.
|
||||
|
||||
![Example Screen](googledeveloper3.png)
|
||||
![](googledeveloper3.png)
|
||||
|
||||
9. Click on **Configure Consent Screen**
|
||||
|
||||
![Example Screen](googledeveloper4.png)
|
||||
![](googledeveloper4.png)
|
||||
|
||||
10. **User Type:** If you do not have a Google Workspace (GSuite) account choose _External_. If you do have a Google Workspace (Gsuite) account and want to limit access to only users inside of your organization choose _Internal_
|
||||
|
||||
|
@ -50,30 +50,29 @@ _I'm only going to list the mandatory/important fields to complete._
|
|||
19. Click **Create Credentials** on the top of the screen
|
||||
20. Choose **OAuth Client ID**
|
||||
|
||||
![Example Screen](googledeveloper5.png)
|
||||
![](googledeveloper5.png)
|
||||
|
||||
21. **Application Type:** Web Application
|
||||
22. **Name:** Choose a name
|
||||
23. **Authorized redirect URIs:** `https://authenik.company/source/oauth/callback/google/`
|
||||
|
||||
![Example Screen](googledeveloper6.png)
|
||||
![](googledeveloper6.png)
|
||||
|
||||
24. Click **Create**
|
||||
25. Copy and store _Your Client ID_ and _Your Client Secret_ for later
|
||||
|
||||
## authentik
|
||||
|
||||
26. Under _Resources -> Sources_ Click **Create Google OAuth Source**
|
||||
26. Under _Directory -> Federation & Social login_ Click **Create Google OAuth Source**
|
||||
|
||||
27. **Name**: Choose a name (For the example I use Google)
|
||||
28. **Slug**: google (If you choose a different slug the URLs will need to be updated to reflect the change)
|
||||
29. **Consumer Key:** Your Client ID from step 25
|
||||
30. **Consumer Secret:** Your Client Secret from step 25
|
||||
31. **Provider Type:** Google
|
||||
|
||||
Here is an example of a complete authentik Google OAuth Source
|
||||
|
||||
![Example Screen](authentiksource.png)
|
||||
![](authentiksource.png)
|
||||
|
||||
Save, and you now have Google as a source.
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ The following placeholders will be used:
|
|||
|
||||
Here is an example of a complete authentik Mailcow OAuth Source
|
||||
|
||||
![Example Screen](mailcow5.png)
|
||||
![](mailcow5.png)
|
||||
|
||||
Save, and you now have Mailcow as a source.
|
||||
|
||||
|
|
46
website/integrations/sources/twitter/index.md
Normal file
46
website/integrations/sources/twitter/index.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: Twitter
|
||||
---
|
||||
|
||||
Allows users to authenticate using their twitter credentials
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
## Twitter
|
||||
|
||||
You will need to create a new project, and OAuth credentials in the Twitter Developer console.
|
||||
|
||||
1. Visit https://developer.twitter.com/ to create a new App
|
||||
2. Select an environment fitting to your use-case
|
||||
3. Give the app a name, for example _authentik_
|
||||
4. Finish setting up the app by clicking **App settings**. Any of the API keys on this screen are not used by authentik.
|
||||
5. Click the **Set up** button
|
||||
|
||||
![](./twitter1.png)
|
||||
|
||||
6. Enable **OAuth 2.0**
|
||||
7. Set **Type of App** to _Web_
|
||||
8. Set **Callback URI / Redirect URL** to `https://authenik.company/source/oauth/callback/twitter/`
|
||||
9. Set **Website URL** to `https://authentik.company`
|
||||
|
||||
![](./twitter2.png)
|
||||
|
||||
10. Confirm with **Save**
|
||||
11. Copy and store **Client ID** and **Client Secret** for later
|
||||
|
||||
## authentik
|
||||
|
||||
1. Under _Directory -> Federation & Social login_ Click **Create Twitter OAuth Source**
|
||||
|
||||
2. **Name**: Choose a name (For the example I use Google)
|
||||
3. **Slug**: twitter (If you choose a different slug the URLs will need to be updated to reflect the change)
|
||||
4. **Consumer Key:** Your Client ID from step 25
|
||||
5. **Consumer Secret:** Your Client Secret from step 25
|
||||
|
||||
:::note
|
||||
For more details on how-to have the new source display on the Login Page see [here](../).
|
||||
:::
|
BIN
website/integrations/sources/twitter/twitter1.png
Normal file
BIN
website/integrations/sources/twitter/twitter1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
website/integrations/sources/twitter/twitter2.png
Normal file
BIN
website/integrations/sources/twitter/twitter2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
|
@ -37,8 +37,8 @@ module.exports = {
|
|||
"services/rocketchat/index",
|
||||
"services/roundcube/index",
|
||||
"services/sentry/index",
|
||||
"services/sssd/index",
|
||||
"services/sonarr/index",
|
||||
"services/sssd/index",
|
||||
"services/tautulli/index",
|
||||
"services/ubuntu-landscape/index",
|
||||
"services/uptime-kuma/index",
|
||||
|
@ -70,6 +70,7 @@ module.exports = {
|
|||
"sources/oauth/index",
|
||||
"sources/plex/index",
|
||||
"sources/saml/index",
|
||||
"sources/twitter/index",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
Reference in a new issue