sources/oauth: migrate twitter to oauth2 (#2893)

This commit is contained in:
Jens L 2022-05-18 00:03:02 +02:00 committed by GitHub
parent 538c2ca4d3
commit 75b0fb3393
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 773 additions and 924 deletions

View file

@ -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:

View file

@ -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"])

View file

@ -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"

View file

@ -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"""

View file

@ -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

View file

@ -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

View file

@ -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`

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View 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](../).
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View file

@ -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",
],
},
],