Skip to content

Commit

Permalink
Merge branch 'main' into web/improve-email-button-labels
Browse files Browse the repository at this point in the history
* main:
  web: bump API Client version (#7246)
  sources/oauth: include default JWKS URLs for OAuth sources (#6992)
  sources/oauth: periodically update OAuth sources' OIDC configuration (#7245)
  website/blogs: Fix sso blog to remove 3rd reason (#7230)
  lifecycle: fix otp_merge migration again (#7244)
  web: bump core-js from 3.33.0 to 3.33.1 in /web (#7243)
  core: bump node from 20 to 21 (#7237)
  web: fix bad comment that was confusing lit-analyze (#7234)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7235)
  core: bump ruff from 0.1.0 to 0.1.1 (#7238)
  core: bump twilio from 8.9.1 to 8.10.0 (#7239)
  web: bump the storybook group in /web with 5 updates (#7240)
  web: bump the wdio group in /tests/wdio with 4 updates (#7241)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7236)
  • Loading branch information
kensternberg-authentik committed Oct 20, 2023
2 parents 98c886b + cbbb638 commit aa0f5e4
Show file tree
Hide file tree
Showing 23 changed files with 1,394 additions and 1,481 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:20 as website-builder
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder

ENV NODE_ENV=production

Expand All @@ -17,7 +17,7 @@ COPY ./SECURITY.md /work/
RUN npm run build-docs-only

# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/node:20 as web-builder
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder

ENV NODE_ENV=production

Expand Down
16 changes: 12 additions & 4 deletions authentik/sources/oauth/api/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class SourceTypeSerializer(PassiveSerializer):
authorization_url = CharField(read_only=True, allow_null=True)
access_token_url = CharField(read_only=True, allow_null=True)
profile_url = CharField(read_only=True, allow_null=True)
oidc_well_known_url = CharField(read_only=True, allow_null=True)
oidc_jwks_url = CharField(read_only=True, allow_null=True)


class OAuthSourceSerializer(SourceSerializer):
Expand All @@ -56,7 +58,11 @@ def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:

def validate(self, attrs: dict) -> dict:
session = get_http_session()
well_known = attrs.get("oidc_well_known_url")
source_type = registry.find_type(attrs["provider_type"])

well_known = attrs.get("oidc_well_known_url") or source_type.oidc_well_known_url
inferred_oidc_jwks_url = None

if well_known and well_known != "":
try:
well_known_config = session.get(well_known)
Expand All @@ -69,20 +75,22 @@ def validate(self, attrs: dict) -> dict:
attrs["authorization_url"] = config["authorization_endpoint"]
attrs["access_token_url"] = config["token_endpoint"]
attrs["profile_url"] = config["userinfo_endpoint"]
attrs["oidc_jwks_url"] = config["jwks_uri"]
inferred_oidc_jwks_url = config["jwks_uri"]
except (IndexError, KeyError) as exc:
raise ValidationError(
{"oidc_well_known_url": f"Invalid well-known configuration: {exc}"}
)

jwks_url = attrs.get("oidc_jwks_url")
# Prefer user-entered URL to inferred URL to default URL
jwks_url = attrs.get("oidc_jwks_url") or inferred_oidc_jwks_url or source_type.oidc_jwks_url
if jwks_url and jwks_url != "":
attrs["oidc_jwks_url"] = jwks_url
try:
jwks_config = session.get(jwks_url)
jwks_config.raise_for_status()
except RequestException as exc:
text = exc.response.text if exc.response else str(exc)
raise ValidationError({"jwks_url": text})
raise ValidationError({"oidc_jwks_url": text})
config = jwks_config.json()
attrs["oidc_jwks"] = config

Expand Down
12 changes: 12 additions & 0 deletions authentik/sources/oauth/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""OAuth source settings"""
from celery.schedules import crontab

from authentik.lib.utils.time import fqdn_rand

CELERY_BEAT_SCHEDULE = {
"update_oauth_source_oidc_well_known": {
"task": "authentik.sources.oauth.tasks.update_well_known_jwks",
"schedule": crontab(minute=fqdn_rand("update_well_known_jwks"), hour="*/3"),
"options": {"queue": "authentik_scheduled"},
},
}
70 changes: 70 additions & 0 deletions authentik/sources/oauth/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""OAuth Source tasks"""
from json import dumps

from requests import RequestException
from structlog.stdlib import get_logger

from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.http import get_http_session
from authentik.root.celery import CELERY_APP
from authentik.sources.oauth.models import OAuthSource

LOGGER = get_logger()


@CELERY_APP.task(bind=True, base=MonitoredTask)
def update_well_known_jwks(self: MonitoredTask):
"""Update OAuth sources' config from well_known, and JWKS info from the configured URL"""
session = get_http_session()
result = TaskResult(TaskResultStatus.SUCCESSFUL, [])
for source in OAuthSource.objects.all().exclude(oidc_well_known_url=""):
try:
well_known_config = session.get(source.oidc_well_known_url)
well_known_config.raise_for_status()
except RequestException as exc:
text = exc.response.text if exc.response else str(exc)
LOGGER.warning("Failed to update well_known", source=source, exc=exc, text=text)
result.messages.append(f"Failed to update OIDC configuration for {source.slug}")
continue
config = well_known_config.json()
try:
dirty = False
source_attr_key = (
("authorization_url", "authorization_endpoint"),
("access_token_url", "token_endpoint"),
("profile_url", "userinfo_endpoint"),
("oidc_jwks_url", "jwks_uri"),
)
for source_attr, config_key in source_attr_key:
# Check if we're actually changing anything to only
# save when something has changed
if getattr(source, source_attr) != config[config_key]:
dirty = True
setattr(source, source_attr, config[config_key])
except (IndexError, KeyError) as exc:
LOGGER.warning(
"Failed to update well_known",
source=source,
exc=exc,
)
result.messages.append(f"Failed to update OIDC configuration for {source.slug}")
continue
if dirty:
LOGGER.info("Updating sources' OpenID Configuration", source=source)
source.save()

for source in OAuthSource.objects.all().exclude(oidc_jwks_url=""):
try:
jwks_config = session.get(source.oidc_jwks_url)
jwks_config.raise_for_status()
except RequestException as exc:
text = exc.response.text if exc.response else str(exc)
LOGGER.warning("Failed to update JWKS", source=source, exc=exc, text=text)
result.messages.append(f"Failed to update JWKS for {source.slug}")
continue
config = jwks_config.json()
if dumps(source.oidc_jwks, sort_keys=True) != dumps(config, sort_keys=True):
source.oidc_jwks = config
LOGGER.info("Updating sources' JWKS", source=source)
source.save()
self.set_status(result)
48 changes: 48 additions & 0 deletions authentik/sources/oauth/tests/test_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Test OAuth Source tasks"""
from django.test import TestCase
from requests_mock import Mocker

from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.tasks import update_well_known_jwks


class TestOAuthSourceTasks(TestCase):
"""Test OAuth Source tasks"""

def setUp(self) -> None:
self.source = OAuthSource.objects.create(
name="test",
slug="test",
provider_type="openidconnect",
authorization_url="",
profile_url="",
consumer_key="",
)

@Mocker()
def test_well_known_jwks(self, mock: Mocker):
"""Test well_known update"""
self.source.oidc_well_known_url = "http://foo/.well-known/openid-configuration"
self.source.save()
mock.get(
self.source.oidc_well_known_url,
json={
"authorization_endpoint": "foo",
"token_endpoint": "foo",
"userinfo_endpoint": "foo",
"jwks_uri": "http://foo/jwks",
},
)
mock.get("http://foo/jwks", json={"foo": "bar"})
update_well_known_jwks() # pylint: disable=no-value-for-parameter
self.source.refresh_from_db()
self.assertEqual(self.source.authorization_url, "foo")
self.assertEqual(self.source.access_token_url, "foo")
self.assertEqual(self.source.profile_url, "foo")
self.assertEqual(self.source.oidc_jwks_url, "http://foo/jwks")
self.assertEqual(
self.source.oidc_jwks,
{
"foo": "bar",
},
)
4 changes: 4 additions & 0 deletions authentik/sources/oauth/types/azure_ad.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ class AzureADType(SourceType):
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
profile_url = "https://graph.microsoft.com/v1.0/me"
oidc_well_known_url = (
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
)
oidc_jwks_url = "https://login.microsoftonline.com/common/discovery/keys"
4 changes: 4 additions & 0 deletions authentik/sources/oauth/types/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ class GitHubType(SourceType):
authorization_url = "https://github.com/login/oauth/authorize"
access_token_url = "https://github.com/login/oauth/access_token" # nosec
profile_url = "https://api.github.com/user"
oidc_well_known_url = (
"https://token.actions.githubusercontent.com/.well-known/openid-configuration"
)
oidc_jwks_url = "https://token.actions.githubusercontent.com/.well-known/jwks"
2 changes: 2 additions & 0 deletions authentik/sources/oauth/types/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ class GoogleType(SourceType):
authorization_url = "https://accounts.google.com/o/oauth2/auth"
access_token_url = "https://oauth2.googleapis.com/token" # nosec
profile_url = "https://www.googleapis.com/oauth2/v1/userinfo"
oidc_well_known_url = "https://accounts.google.com/.well-known/openid-configuration"
oidc_jwks_url = "https://www.googleapis.com/oauth2/v3/certs"
2 changes: 2 additions & 0 deletions authentik/sources/oauth/types/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class SourceType:
authorization_url: Optional[str] = None
access_token_url: Optional[str] = None
profile_url: Optional[str] = None
oidc_well_known_url: Optional[str] = None
oidc_jwks_url: Optional[str] = None

def icon_url(self) -> str:
"""Get Icon URL for login"""
Expand Down
39 changes: 19 additions & 20 deletions lifecycle/system_migrations/otp_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,22 @@ def needs_migration(self) -> bool:
return bool(self.cur.rowcount)

def run(self):
with self.con.transaction():
self.cur.execute(SQL_STATEMENT)
self.fake_migration(
(
"authentik_stages_authenticator_static",
"0008_initial",
),
(
"authentik_stages_authenticator_static",
"0009_throttling",
),
(
"authentik_stages_authenticator_totp",
"0008_initial",
),
(
"authentik_stages_authenticator_totp",
"0009_auto_20190420_0723",
),
)
self.cur.execute(SQL_STATEMENT)
self.fake_migration(
(
"authentik_stages_authenticator_static",
"0008_initial",
),
(
"authentik_stages_authenticator_static",
"0009_throttling",
),
(
"authentik_stages_authenticator_totp",
"0008_initial",
),
(
"authentik_stages_authenticator_totp",
"0009_auto_20190420_0723",
),
)
42 changes: 21 additions & 21 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion proxy.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:20 as web-builder
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder

ENV NODE_ENV=production
WORKDIR /static
Expand Down
10 changes: 10 additions & 0 deletions schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40934,10 +40934,20 @@ components:
type: string
readOnly: true
nullable: true
oidc_well_known_url:
type: string
readOnly: true
nullable: true
oidc_jwks_url:
type: string
readOnly: true
nullable: true
required:
- access_token_url
- authorization_url
- name
- oidc_jwks_url
- oidc_well_known_url
- profile_url
- request_token_url
- slug
Expand Down
Loading

0 comments on commit aa0f5e4

Please sign in to comment.