Compare commits

...

168 Commits

Author SHA1 Message Date
Jens L.
718e8ce93f web: fix tree-sitter dep for 2025.10 (#20902)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2026-03-14 16:38:59 +01:00
Marc 'risson' Schmitt
4af088740f ci: pull latest changes before tagging new version (cherry-pick #20413 to version-2025.10) (#20416) 2026-02-19 14:32:29 +01:00
Marc 'risson' Schmitt
1ee10c7359 ci: fix setup altering package-lock (cherry-pick #20348 to version-2025.10) (#20354)
ci: fix setup altering package-lock (#20348)

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
2026-02-17 13:14:06 +01:00
authentik-automation[bot]
4bdfcbb47f root: do not rely on npm cli for version bump (cherry-pick #20276 to version-2025.10) (#20319)
root: do not rely on npm cli for version bump

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-17 13:02:20 +01:00
Marc 'risson' Schmitt
01d8d7cdbf ci: fix binary outpost build on release (cherry-pick #20248 to version-2025.10) (#20281)
fix binary outpost build on release (#20248)
2026-02-16 13:36:08 +01:00
authentik-automation[bot]
7b0f19465b release: 2025.10.4 2026-02-12 15:59:04 +00:00
Ken Sternberg
8779d7132c web: updated package-lock.json to include missing tree-sitter references. (#20247)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-12 16:23:46 +01:00
Marc 'risson' Schmitt
4b5050abd5 website/docs: fix lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-12 15:40:30 +01:00
Marc 'risson' Schmitt
be4ed7c779 lib: fix lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-12 15:40:22 +01:00
authentik-automation[bot]
c385d09dd9 security: CVE-2026-25227 (#20227)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:26:43 +01:00
authentik-automation[bot]
4b88c62f9f security: CVE-2026-25748 (#20228)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:26:30 +01:00
authentik-automation[bot]
88a056af5d security: CVE-2026-25922 (#20229)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:26:17 +01:00
authentik-automation[bot]
8db54884a0 website/docs: rac: fixes the property mapping formatting (cherry-pick #20200 to version-2025.10) (#20201)
website/docs: rac: fixes the property mapping formatting (#20200)

Fixes the property mapping formatting

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-11 19:30:21 +00:00
authentik-automation[bot]
32085291f0 website/docs: add email verification scope doc (cherry-pick #20141 to version-2025.10) (#20204)
website/docs: add email verification scope doc (#20141)

* WIP

* Add link to 2025.10 release notes

* Apply suggestions from code review




---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-02-11 17:01:20 +00:00
authentik-automation[bot]
a1c7c9a163 outpost/proxyv2: reduce max number of postgres connections (cherry-pick #19211 to version-2025.10) (#20139)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-10 12:39:09 +01:00
authentik-automation[bot]
f626d26c56 website/docs: generate CVE sidebar (cherry-pick #20098 to version-2025.10) (#20100)
website/docs: generate CVE sidebar (#20098)

* website/docs: generate CVE sidebar



* docs



* slightly less warnings



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-02-08 17:09:37 +01:00
authentik-automation[bot]
99cff7e93e outpost/proxyv2: revalidate auth if session fails to load (cherry-pick #18063 to version-2025.10) (#20058)
outpost/proxyv2: revalidate auth if session fails to load (#18063)

Co-authored-by: Chetan Sarva <chetan@pixelcop.net>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-02-06 00:43:53 +01:00
Marcelo Elizeche Landó
f2188d00f9 core: bump django from v5.2.8 to 5.2.11 (version-2025.10) (#20020)
* bump django from v5.2.8 to 5.2.11

* Use MAX_URL_REDIRECT_LENGTH since MAX_URL_LENGTH was removed in nov 25

* fix using django.utils.http as same as main
2026-02-05 14:41:40 -03:00
authentik-automation[bot]
8a7129d74e docs: add instructions for configuring rp-initiated single logout (cherry-pick #20040 to version-2025.10) (#20054)
docs: add instructions for configuring rp-initiated single logout (#20040)

Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2026-02-05 16:39:03 +00:00
authentik-automation[bot]
1e96e0e639 website/docs: Remove stale 2024 version directives (cherry-pick #19888 to version-2025.10) (#20022)
* Cherry-pick #19888 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #19888
Original commit: 469bc0b6b4

* fix conflicts

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-02-04 22:07:18 +01:00
authentik-automation[bot]
13bb8e3145 providers/oauth2: use compare_digest for client_secret comparison (cherry-pick #19979 to version-2025.10) (#19982)
providers/oauth2: use compare_digest for client_secret comparison (#19979)

* security: use constant-time comparison for client secrets

Replace insecure '!=' comparisons with hmac.compare_digest() to prevent
timing attacks on client secret validation. This matches the existing
security pattern used elsewhere in the codebase.

* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Kolega.dev <security@kolega.ai>
Co-authored-by: kolega.dev <faizan@kolega.ai>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-02-03 19:43:01 +01:00
authentik-automation[bot]
7a52df2901 recovery: consume token in transaction (cherry-pick #19967 to version-2025.10) (#19981)
recovery: consume token in transaction (#19967)

security: prevent recovery token reuse via race condition

Token validation, user login, and token deletion were performed as
separate non-atomic operations, allowing concurrent requests to reuse
a single recovery token. Wrapped the operation in transaction.atomic()
with select_for_update() to ensure exclusive access during token use.

Co-authored-by: Kolega.dev <security@kolega.ai>
Co-authored-by: kolega.dev <faizan@kolega.ai>
2026-02-03 19:42:42 +01:00
Marc 'risson' Schmitt
6d8554870f root: update client-go generation (cherry-pick #19762 and #19906 to version-2025.10) (#19933)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-02-03 17:11:59 +01:00
authentik-automation[bot]
aafb7cb7dc website/docs: fix Transifex link in translation guide (cherry-pick #19735 to version-2025.10) (#19770)
website/docs: fix Transifex link in translation guide (#19735)

Closes https://github.com/goauthentik/authentik/issues/19730

Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-27 17:46:29 +01:00
authentik-automation[bot]
241e674b64 web/sfe: downgrade bootstrap, add access denied test (cherry-pick #19763 to version-2025.10) (#19764)
Cherry-pick #19763 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #19763
Original commit: cdd3fb7827

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-26 17:23:14 +01:00
authentik-automation[bot]
b72e3b55a0 web/admin: fix impersonation form requesting data without being opened (cherry-pick #19673 to version-2025.10) (#19711)
* Cherry-pick #19673 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #19673
Original commit: 0a10b81d1d

* fix conflict

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-23 21:46:56 +01:00
authentik-automation[bot]
f758ed2c17 core: return bad request when user is authenticated and not active (cherry-pick #19706 to version-2025.10) (#19709)
core: return bad request when user is authenticated and not active (#19706)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-23 20:59:24 +01:00
authentik-automation[bot]
00fad79ea3 internal: fix incorrect metric calculation (cherry-pick #19701 to version-2025.10) (#19702)
internal: fix incorrect metric calculation (#19701)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-23 17:17:43 +01:00
authentik-automation[bot]
ac0df081c1 providers/oauth2: add logout+jwt token type for oidc logout token. (cherry-pick #19554 to version-2025.10) (#19674)
providers/oauth2: add `logout+jwt` token type for oidc logout token. (#19554)

* providers/oauth2: add `logout+jwt` token type for oidc logout token.

The oidc back-channel logout spec recommends using explicitly typed JWTs using the `typ` parameter in the JWT's header.

[spec](https://openid.net/specs/openid-connect-backchannel-1_0.html#CrossJWT)

This may be a breaking change for some implementations if they were already checking the type of the token to be `JWT` (the default value).

* Apply suggestion from @BeryJu



---------

Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jeroen <jeroen@velzen.cc>
Co-authored-by: Jens L. <jens@beryju.org>
2026-01-22 15:24:04 +01:00
authentik-automation[bot]
bcefa8b7a1 website/docs: limiting permissions of AD service account (cherry-pick #19483 to version-2025.10) (#19488)
website/docs: limiting permissions of AD service account (#19483)

* Add info about limiting permissions

* Simplified instructions

* OU > organizational unit

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-01-15 16:34:25 +00:00
authentik-automation[bot]
fb8cb21967 website/docs: mention dynamic overrides in redirect stage documentation (cherry-pick #19368 to version-2025.10) (#19401)
website/docs: mention dynamic overrides in redirect stage documentation (#19368)

Signed-off-by: Severin Schoepke <severin@users.noreply.github.com>
Co-authored-by: Severin Schoepke <severin@users.noreply.github.com>
2026-01-14 14:44:48 +01:00
authentik-automation[bot]
170b6619df website/docs: add import to discord policy (cherry-pick #19397 to version-2025.10) (#19405)
website/docs: add import to discord policy (#19397)

Add import line

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2026-01-14 13:26:31 +00:00
authentik-automation[bot]
a8c4c4e70d website/docs: update gws provider docs (cherry-pick #18286 to version-2025.10) (#19399)
website/docs: update gws provider docs (#18286)

* Update filenames, sidebar and redirect. Rework overview doc

* WIP

* Spelling

* Move info box

* WIP

* Update create-gws-provider.md



* Small tweaks

* Add note about key creation

* Update website/docs/add-secure-apps/providers/gws/configure-gws.md




* Add delegated user permissions

* Update configure-gws.md



* Fix link and section naming

* Apply suggestions from code review




* Update configure-gws.md



* Update website/docs/add-secure-apps/providers/gws/index.md




* Update website/docs/add-secure-apps/providers/gws/index.md




* Headers

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2026-01-14 11:35:06 +00:00
authentik-automation[bot]
c243fe4914 internal: rework liveness probe and proxy (cherry-pick #19312 to version-2025.10) (#19383)
internal: rework liveness probe and proxy (#19312)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-13 19:20:51 +01:00
authentik-automation[bot]
6402010292 outpost/proxyv2: fix stale session cookie causing 400 error in createState (cherry-pick #19026 to version-2025.10) (#19374)
outpost/proxyv2: fix stale session cookie causing 400 error in createState (#19026)

Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-13 17:36:28 +01:00
authentik-automation[bot]
e35984096d web/elements: hidden secrets not propagating (cherry-pick #19029 to version-2025.10) (#19376)
web/elements: hidden secrets not propagating (#19029)

* web: Add InvalidationFlow to Radius Provider dialogues

## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.

* This (temporary) change is needed to prevent the unit tests from failing.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes

* Revert "This (temporary) change is needed to prevent the unit tests from failing."

This reverts commit dddde09be5.

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

* web/bug/hidden-secrets-not-propagating

# What

This commit updates ak-secret-text-input, adding the `name` attribute to all valid input fields and updating the value writer to match those of known-working components, to ensure that either variety of the display is fully and correctly updated with the content of the hidden secret.

# Why

The hidden input field is the one that HorizontalFormElement was expecting to read its value from, but that field never received a `name` because it wasn’t present when the field was first updated.

HorizontalFormElement writes the `name` field to the first `<input>` it finds. That was the “dummy” input field, which has no working value.

Form ignored the input element because the value it read came with an undefined name.

Object-oriented state management sometimes bites.

---------

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2026-01-13 17:36:23 +01:00
authentik-automation[bot]
b1272150b9 website/docs: update LDAP provider docs (cherry-pick #18272 to version-2025.10) (#19344)
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-13 13:48:03 +01:00
authentik-automation[bot]
e44cf378d7 website/docs: update m2m doc (cherry-pick #18963 to version-2025.10) (#19323)
website/docs: update m2m doc (#18963)

* Updates m2m doc, add mention to proxy provider about finding logs, updates filename/links/redirects

* Apply suggestions from code review




* Prettier

* wip

* Removed section and changed some wording

* Add section

* Update website/docs/add-secure-apps/providers/proxy/index.md




---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-11 23:00:16 +00:00
authentik-automation[bot]
653a0ba794 website/docs: Fix documentation example for app_entitlements_attributes. (cherry-pick #19316 to version-2025.10) (#19325)
website/docs: Fix documentation example for `app_entitlements_attributes`. (#19316)

Fix example for `app_entitlements_attributes`.

Fix example Python code for `app_entitlements_attributes`.

Signed-off-by: Sebastian Wiesinger <sebastian@karotte.org>
Co-authored-by: Sebastian Wiesinger <sebastian@karotte.org>
2026-01-11 22:21:03 +00:00
authentik-automation[bot]
dfa5378804 website/docs: Fix typo in GitHub OAuth Source instructions (cherry-pick #18936 to version-2025.10) (#19321)
website/docs: Fix typo in GitHub OAuth Source instructions (#18936)

Co-authored-by: Tom Crasset <25140344+tcrasset@users.noreply.github.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-01-11 21:58:08 +00:00
authentik-automation[bot]
eec0ca4907 website/docs: update entra id provider docs (cherry-pick #18366 to version-2025.10) (#19255)
website/docs: update entra id provider docs (#18366)

* Updates doc filenames, sidebar, redirects and doc content

* Apply suggestions

* Apply suggestions

* Apply suggestions

* Update index.md



* Apply suggestions

* Apply suggestions

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-08 20:10:57 +00:00
authentik-automation[bot]
b95312b13b web/admin: add banner to flow import form (cherry-pick #19288 to version-2025.10) (#19292)
web/admin: add banner to flow import form (#19288)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-01-08 19:52:35 +01:00
authentik-automation[bot]
fb708188bc core: fix read replica routing during transactions (cherry-pick #19086 to version-2025.10) (#19240)
Co-authored-by: Dominic R <dominic@sdko.org>
fix read replica routing during transactions (#19086)
2026-01-08 13:18:21 +01:00
authentik-automation[bot]
e084c629a7 web: fix slug auto-updating when editing existing applications (cherry-pick #19169 to version-2025.10) (#19172)
web: fix slug auto-updating when editing existing applications (#19169)

Co-authored-by: Dominic R <dominic@sdko.org>
2026-01-04 04:22:08 +00:00
authentik-automation[bot]
cd04a205b4 website/docs: fix build (cherry-pick #19148 to version-2025.10) (#19150)
website/docs: fix build (#19148)

* ensure we never throw errors in the browser



* cleaner



* rework



* fix misleading variable



* Tidy behavior.

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2026-01-01 20:07:53 +01:00
authentik-automation[bot]
bfdd00a622 internal: update TLS Suite (cherry-pick #19076 to version-2025.10) (#19077)
* internal: update TLS Suite (#19076)

* internal: update TLS Suite

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* disable chacha20 due to fips

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* match go version

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-28 15:39:45 +01:00
authentik-automation[bot]
1358eed96c core: use chunked_queryset for expired message deletion (cherry-pick #19028 to version-2025.10) (#19030)
core: use chunked_queryset for expired message deletion (#19028)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-24 01:56:28 +01:00
authentik-automation[bot]
467321f570 web/admin: Fix haveibeenpwned link in PasswordPolicyForm (cherry-pick #18984 to version-2025.10) (#18988)
web/admin: Fix haveibeenpwned link in PasswordPolicyForm (#18984)

web: Fix haveibeenpwned link in PasswordPolicyForm

Co-authored-by: Henry Skrtich <1214484+hskrtich@users.noreply.github.com>
2025-12-21 15:46:41 +01:00
authentik-automation[bot]
94f7a6d45d web/admin: fix dark theme on map (cherry-pick #18985 to version-2025.10) (#18986)
web/admin: fix dark theme on map (#18985)

web/admin: fix dark theme on map broken

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-21 15:44:34 +01:00
authentik-automation[bot]
c29c0de498 website/docs: Backport version picker updates. (cherry-pick #18964 to version-2025.10) (#18974)
* website: Unlisted Release Notes

* Swizzle unlisted component. Revise copy for pre-release.

* website/docs: Backport version picker updates.

Fix import path.

Show unlisted entries if release.

Fix sidebar rendering.

Fix positioning of pre-release note. Tidy phrasing.

Clarify pre-release vs draft.

website/docs: Fix version parsing.

* Normalize labels.

---------

Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-20 07:58:46 -05:00
authentik-automation[bot]
f8060de2f0 website/docs: add note to active directory source doc (cherry-pick #18787 to version-2025.10) (#18965)
website/docs: add note to active directory source doc (#18787)

Adds note

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-19 15:38:15 +00:00
authentik-automation[bot]
9bf58f9c22 web/flow: Fix spurious double submit on ak-stage-autosubmit (cherry-pick #18727 to version-2025.10) (#18932)
web/flow: Fix spurious double submit  on ak-stage-autosubmit (#18727)

* Fix double submission on ak-stage-autosubmit

* use updated correctly



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Victor Nawothnig <dminuoso@icloud.com>
Co-authored-by: Victor Nawothnig <Victor.Nawothnig+git@icloud.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-18 16:25:41 +01:00
authentik-automation[bot]
0d617e4ad1 release: 2025.10.3 2025-12-16 17:51:21 +00:00
authentik-automation[bot]
4adc0eaf8e website/docs: 2025.10.3 release notes (cherry-pick #18868 to version-2025.10) (#18872)
website/docs: 2025.10.3 release notes (#18868)

* website/docs: 2025.10.3 release notes



* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-16 17:05:56 +01:00
authentik-automation[bot]
7de405db6d website/docs: add icon info to style guide (cherry-pick #18832 to version-2025.10) (#18834)
website/docs: add icon info to style guide (#18832)

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-12-15 14:34:02 +00:00
authentik-automation[bot]
50b291d6c4 packages/django-dramatiq-postgres: broker: close django connections on consumer close (cherry-pick #18833 to version-2025.10) (#18835)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Norman Ziebal <norman.ziebal@mail.schwarz>
close django connections on consumer close (#18833)
2025-12-15 15:01:30 +01:00
authentik-automation[bot]
14005fe781 core: list applications fix (cherry-pick #18798 to version-2025.10) (#18827)
Co-authored-by: Ryan Pesek <44002516+ryanpesek@users.noreply.github.com>
fix (#18798)
2025-12-15 12:36:38 +00:00
authentik-automation[bot]
591153b6cd core: optimize list applications (cherry-pick #18330 to version-2025.10) (#18791)
Co-authored-by: Ryan Pesek <44002516+ryanpesek@users.noreply.github.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-12 17:54:33 +01:00
authentik-automation[bot]
864856733e outpost/proxyv2: more tests, fix pg password with spaces, and existing session on restart (cherry-pick #18211 to version-2025.10) (#18742)
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
fix pg password with spaces, and existing session on restart (#18211)
2025-12-11 16:00:11 +01:00
authentik-automation[bot]
1b66803a31 website/docs: background tasks: add more detail about "next run" (cherry-pick #18660 to version-2025.10) (#18672)
website/docs: background tasks: add more detail about "next run" (#18660)

Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-12-11 12:29:25 +01:00
authentik-automation[bot]
d8579b02ed web: Improved table selection behavior (cherry-pick #18622 to version-2025.10) (#18685)
web: Improved table selection behavior (#18622)

* Fix caching issues when selecting a row.

* Adjust scroll alignment.

* Fix typo.

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-09 17:40:18 +00:00
authentik-automation[bot]
f98d464323 web: Hide device picker when challenges are not present. (cherry-pick #18611 to version-2025.10) (#18681)
Cherry-pick #18611 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #18611
Original commit: 4df1345c01

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-09 12:22:23 -05:00
authentik-automation[bot]
7828facc41 root: skip current tab when refreshing others (cherry-pick #18674 to version-2025.10) (#18675)
root: skip current tab when refreshing others (#18674)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-08 15:11:00 +01:00
authentik-automation[bot]
ffe2bde51f website/docs: install-config: fix dump_config command (cherry-pick #18659 to version-2025.10) (#18671)
website/docs: install-config: fix dump_config command (#18659)

Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-08 07:02:11 -05:00
authentik-automation[bot]
f6dcdd059c sources/ldap: make server info optional (cherry-pick #18648 to version-2025.10) (#18654)
sources/ldap: make server info optional (#18648)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-07 17:49:23 +01:00
authentik-automation[bot]
2629759293 web/admin: fix event volume chart not updating with query (cherry-pick #18649 to version-2025.10) (#18653)
web/admin: fix event volume chart not updating with query (#18649)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-07 17:04:50 +01:00
authentik-automation[bot]
1b9bd8d4af web: Fix row expansion on modal trigger buttons. (cherry-pick #18412 to version-2025.10) (#18647)
web: Fix row expansion on modal trigger buttons. (#18412)

web: Fix row expansion on modal triggers.

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-12-06 19:49:23 +00:00
authentik-automation[bot]
c0e5ac3127 enterprise/stages/mtls: fix traefik certificate parsing (cherry-pick #18607 to version-2025.10) (#18645)
* Cherry-pick #18607 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #18607
Original commit: 6d7249ea56

* resolve conflict

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-06 16:45:47 +01:00
authentik-automation[bot]
53f4bd613f root: fix missing authentik_device cookie causing error (cherry-pick #18642 to version-2025.10) (#18644)
root: fix missing authentik_device cookie causing error (#18642)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-06 03:01:49 +01:00
authentik-automation[bot]
83e41efe07 flows: refresh unauthenticated tabs (cherry-pick #18621 to version-2025.10) (#18633)
* Cherry-pick #18621 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #18621
Original commit: 31186baf25

* fix conflict

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-05 19:35:10 +01:00
authentik-automation[bot]
ad569be1d5 website/docs: adds note about ak_create_jwt function (cherry-pick #18614 to version-2025.10) (#18626)
website/docs: adds note about ak_create_jwt function (#18614)

* Adds note

* Apply suggestion from @tanberry




---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-12-05 09:52:51 +00:00
authentik-automation[bot]
064866ccc7 website/docs: expressions: fix markdown (cherry-pick #18613 to version-2025.10) (#18617)
website/docs: expressions: fix markdown (#18613)

Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-04 18:36:48 +00:00
authentik-automation[bot]
36593d4700 web/admin: fix brands default switch label (cherry-pick #18518 to version-2025.10) (#18522)
Co-authored-by: Marcin Koziuk <marcin.koziuk@gmail.com>
fix brands default switch label (#18518)
2025-12-02 17:27:14 +01:00
authentik-automation[bot]
2857e4df95 providers/scim: compare users/groups before sending update request (cherry-pick #18456 to version-2025.10) (#18465)
providers/scim: compare users/groups before sending update request (#18456)

* implement user



* add compare for groups



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-12-01 15:09:19 +01:00
authentik-automation[bot]
28b4a927ef website/docs: update certificate doc (cherry-pick #18295 to version-2025.10) (#18326)
website/docs: update certificate doc (#18295)

* Update line

* Add expiry information

* Apply suggestion from @dominic-r




* Apply suggestions

* Improve language

* Apply suggestions

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-27 18:09:23 +00:00
authentik-automation[bot]
7a20845a03 web/admin: fixes capitalization in application wizard title (cherry-pick #17959 to version-2025.10) (#17962)
web/admin: fixes capitalization in application wizard title (#17959)

Changes 'The' to 'the'

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-27 18:17:26 +01:00
authentik-automation[bot]
76ca2fbf77 web: Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18408)
* web: Table row refinements (#17659)

* web: Reset selection state after refresh.

* web: Only select row when not expandable.

* web: Only render expandable content when row is expanded.

* web: Use `repeat` directive.

* web: Fix nested pointer event detection.

* web: Fix issues surrounding stale table rows.

* Port row selector fix.

---------

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-11-27 16:55:31 +00:00
authentik-automation[bot]
e4e8bc57f1 website/docs: improve creds recovery docs (cherry-pick #18385 to version-2025.10) (#18411)
website/docs: improve creds recovery docs (#18385)

* Updates doc

* Fix links

* Typo

* Email link update

* Update website/docs/users-sources/user/user_basic_operations.md




---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-27 14:36:58 +00:00
authentik-automation[bot]
15380dee37 packages/django-channels-postgres: fix notify size check (cherry-pick #18347 to version-2025.10) (#18409)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix notify size check (#18347)
2025-11-27 14:39:48 +01:00
authentik-automation[bot]
b4844f8800 stages/prompt: set allow_blank for _read_only fields (cherry-pick #18297 to version-2025.10) (#18406)
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-27 13:20:19 +00:00
Marc 'risson' Schmitt
e9ff4f79ca web: revert Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18407)
Fix stale table rows (cherry-pick #17940 to version-2025.10) (#1…"
2025-11-27 14:08:33 +01:00
authentik-automation[bot]
92fb2f0f2b web: Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18373)
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-27 14:07:50 +01:00
authentik-automation[bot]
f80ce9dd6c web/admin: fix wording in password stage (cherry-pick #18393 to version-2025.10) (#18395)
web/admin: fix wording in password stage (#18393)

Remove word

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-26 19:03:48 +01:00
authentik-automation[bot]
a233feec29 lib/sync/outgoing: check if there is a provider before creating tasks (cherry-pick #18394 to version-2025.10) (#18397)
lib/sync/outgoing: check if there is a provider before creating tasks (#18394)

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-26 18:01:42 +00:00
authentik-automation[bot]
bc9215a2ff web/admin: add entitlement search (cherry-pick #18291 to version-2025.10) (#18390)
web/admin: add entitlement search (#18291)

* web/admin: add entitlement search



* Apply suggestion from @GirlBossRush



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-26 14:48:27 +01:00
authentik-automation[bot]
263a2bca6d outposts: set container healthcheck inline (cherry-pick #18298 to version-2025.10) (#18370)
outposts: set container healthcheck inline (#18298)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-26 00:12:20 +01:00
authentik-automation[bot]
4cc71ef161 website/docs: update info about docker socket mount (cherry-pick #18344 to version-2025.10) (#18365)
website/docs: update info about docker socket mount (#18344)

* Update info about docker socket mounting

* Apply suggestions from code review





* Update website/docs/install-config/install/docker-compose.mdx




---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-11-25 12:46:50 +00:00
authentik-automation[bot]
f66c535ae0 website/docs: add high availability doc (cherry-pick #18182 to version-2025.10) (#18325)
website/docs: add high availability doc (#18182)

* Create document and intro

* Add high availability document and update sidebar

* Spelling and header

* Add mermaid diagram

* Applied suggestions

* More suggestions

* Fix links

* Update website/docs/install-config/high-availability.mdx




* Update website/docs/install-config/high-availability.mdx




* Apply suggestions

* Add monitoring link

* Apply Ken's suggestion

* Apply suggestion from @dominic-r



* Apply suggestion from @dominic-r



* Apply suggestions

* Few wording changes

* Wording improvements

* Apply suggestions from code review




* Apply suggestion



* Apply suggestion from @tanberry




* Apply suggestions

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-11-21 19:37:24 +00:00
authentik-automation[bot]
893325a7b7 website/docs: added missed edits on Blueprints docs (cherry-pick #18321 to version-2025.10) (#18324)
website/docs: added missed edits on Blueprints docs (#18321)

added missed edits

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-11-21 14:03:08 -05:00
authentik-automation[bot]
a62c73d6f1 website/docs: enhance blueprint docs (cherry-pick #15984 to version-2025.10) (#18322)
website/docs: enhance blueprint docs (#15984)

* draft of note about bp behviour when modified

* Update website/docs/customize/blueprints/index.mdx




* clarify title

* more tweaks

* tweaks

* more content, rearranged headings

* tweak

* more content about creating a bp instance

* create new page for procedures

* tweaks

* add to sidebar, tweaks

* fixed conflict

* add link to procedurals

* typo

* more content

* more links, more tips

* wip

* Apply suggestion from @dominic-r



* Apply suggestion from @dominic-r



* final tweaks

* jens and dewi edits

* tweaks

* more Dewi and Jens edits yay

---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-21 15:53:58 +00:00
authentik-automation[bot]
483710a59c website/docs: further improvments to source switch doc (cherry-pick #18320 to version-2025.10) (#18323)
website/docs: further improvments to source switch doc (#18320)

Moves section and improves language

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-21 14:47:10 +00:00
authentik-automation[bot]
b8b7584e8e website/docs: fix broken link in source switching doc (cherry-pick #18317 to version-2025.10) (#18319)
website/docs: fix broken link in source switching doc (#18317)

Fix link and policy example

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-21 13:42:47 +00:00
authentik-automation[bot]
2fedc3d0a0 release: 2025.10.2 2025-11-19 15:07:06 +00:00
authentik-automation[bot]
7f0b45f921 website/docs: add 2025.8.5 and 2025.10.2 release notes (cherry-pick #18268 to version-2025.10) (#18270)
website/docs: add 2025.8.5 and 2025.10.2 release notes (#18268)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-19 15:29:42 +01:00
authentik-automation[bot]
3905c281ad internal: Automated internal backport: 5000-sidebar.sec.patch to authentik-2025.10 (#18260)
Automated internal backport of patch 5000-sidebar.sec.patch to authentik-2025.10

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-19 15:10:38 +01:00
authentik-automation[bot]
e6099d43f5 internal: Automated internal backport: 1498-oauth2-cc-user-active.sec.patch to authentik-2025.10 (#18259)
Automated internal backport of patch 1498-oauth2-cc-user-active.sec.patch to authentik-2025.10

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-11-19 14:51:31 +01:00
authentik-automation[bot]
a91145bc7b internal: Automated internal backport: 1487-invitation-expiry.sec.patch to authentik-2025.10 (#18258)
Automated internal backport of patch 1487-invitation-expiry.sec.patch to authentik-2025.10

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-11-19 14:51:03 +01:00
authentik-automation[bot]
3f38d5c7d9 stages/prompt: fix choices with labels causing error on submit (cherry-pick #18183 to version-2025.10) (#18236)
stages/prompt: fix choices with labels causing error on submit (#18183)

* stages/prompt: fix choices with labels causing error on submit



* fix tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-18 18:33:10 +01:00
authentik-automation[bot]
c00df0573c website/docs: update application description (cherry-pick #18125 to version-2025.10) (#18127)
website/docs: update application description (#18125)

Update due to 2025.10 changes

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-18 16:34:55 +00:00
authentik-automation[bot]
c3a0edee00 website/docs: Add instructions for installing RC versions (cherry-pick #18099 to version-2025.10) (#18193)
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-17 23:52:39 +00:00
authentik-automation[bot]
8b81ca36ea web/sfe: downgrade bootstrap that was accidentally upgraded (cherry-pick #18157 to version-2025.10) (#18171)
* Cherry-pick #18157 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #18157
Original commit: 4caece7fef

* fix conflict

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-16 20:03:40 +01:00
authentik-automation[bot]
698de68a36 web: Disable library <datalist> on Firefox. (cherry-pick #18103 to version-2025.10) (#18135)
Cherry-pick #18103 to version-2025.10 (with conflicts)
This cherry-pick has conflicts that need manual resolution.

Original PR: #18103
Original commit: 1115e6f

Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-14 19:41:50 +01:00
authentik-automation[bot]
db35593b24 packages/django-channels-postgres/layer: fix query when subscribed to multiple channels (cherry-pick #18152 to version-2025.10) (#18153)
packages/django-channels-postgres/layer: fix query when subscribed to multiple channels (#18152)

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-14 19:41:12 +01:00
authentik-automation[bot]
445fa31b57 web/admin: link to user on invitation list page (cherry-pick #18132 to version-2025.10) (#18134)
web/admin: link to user on invitation list page (#18132)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-13 22:38:06 +01:00
authentik-automation[bot]
a9aa1bf2c2 web/flows: improvements for hCaptcha (cherry-pick #16882 to version-2025.10) (#18128)
web/flows: improvements for hCaptcha (#16882)

* improvements for hCaptcha
Issue #16755

* web: Format.

---------

Co-authored-by: Tealk <12276250+Tealk@users.noreply.github.com>
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-11-13 21:02:26 +01:00
authentik-automation[bot]
d018f0381c packages/django-dramatiq-postgres: broker: ensure locking happens with the same connection (cherry-pick #18095 to version-2025.10) (#18119)
packages/django-dramatiq-postgres: broker: ensure locking happens with the same connection (#18095)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-13 17:18:13 +00:00
authentik-automation[bot]
7dd1cd5c59 website/docs: fix wording in stages overview (cherry-pick #18061 to version-2025.10) (#18120)
website/docs: fix wording in stages overview (#18061)

Change flow to stage

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-13 16:03:31 +00:00
authentik-automation[bot]
c219a6804a web: Fix tab activation, blank provider URLs (cherry-pick #18031 to version-2025.10) (#18101)
web: Fix tab activation, blank provider URLs (#18031)

web: Fix tab activation.

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-13 12:47:38 +01:00
authentik-automation[bot]
d9310d04b0 web: Fix RAC modal visibility. (cherry-pick #17941 to version-2025.10) (#18097)
web: Fix RAC modal visibility. (#17941)

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-11-12 23:22:02 +01:00
authentik-automation[bot]
f471ef0e2e cmd/server/healthcheck: remove worker HTTP healthcheck (cherry-pick #18090 to version-2025.10) (#18091)
cmd/server/healthcheck: remove worker HTTP healthcheck (#18090)

* cmd/server/healthcheck: remove worker HTTP healthcheck



* lint



---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-12 16:12:17 +01:00
authentik-automation[bot]
31a010c108 core: improve app launch URL formatting (cherry-pick #18076 to version-2025.10) (#18087)
core: improve app launch URL formatting (#18076)

* core: improve app launch URL formatting



* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-12 13:06:46 +01:00
authentik-automation[bot]
96e6ab291e providers/scim: allow custom schema data (cherry-pick #18073 to version-2025.10) (#18075)
providers/scim: allow custom schema data (#18073)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-12 00:54:08 +01:00
authentik-automation[bot]
ebf68311c2 events: fix timezone not set for log events (cherry-pick #18067 to version-2025.10) (#18071)
events: fix timezone not set for log events (#18067)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-11 21:20:06 +01:00
Jens L.
fd365b2a09 ci: revert to upstream GHA for release (#18058) (#18065)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-11 18:50:24 +01:00
authentik-automation[bot]
41104da41f ci: attempt to fix integration tests using dind (cherry-pick #18066 to version-2025.10) (#18069)
ci: attempt to fix integration tests using dind (#18066)

* ci: attempt to fix integration tests using dind



* bump dind version



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-11 18:18:53 +01:00
authentik-automation[bot]
7edebdec03 website/docs: update discord social login script example (cherry-pick #18026 to version-2025.10) (#18057)
website/docs: update discord social login script example (#18026)

update the guild membership example to no longer cause an exception from a missing import.

Closes #18025

Signed-off-by: TMUniversal <10200399+TMUniversal@users.noreply.github.com>
Co-authored-by: TMUniversal <10200399+TMUniversal@users.noreply.github.com>
2025-11-11 13:02:48 +01:00
authentik-automation[bot]
fb56a54eb1 website/release notes: fix broken urls (cherry-pick #18041 to version-2025.10) (#18044)
website/release notes: fix broken urls (#18041)

* website: fix bad escaping of URLs in release notes

## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2025-11-10 15:50:29 -05:00
Jens L.
31cd6eb8ce ci: fix migrate-from-stable for old versions (#18019) (#18024)
ci: better logic for picking previous stable version

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-10 15:56:45 +01:00
authentik-automation[bot]
092c5eb33c website/docs: updates img-src csp (cherry-pick #18010 to version-2025.10) (#18012) 2025-11-06 21:11:37 +00:00
authentik-automation[bot]
3e41bba54d core: bump django from 5.2.7 to 5.2.8 (cherry-pick #17967 to version-2025.10) (#18003)
core: bump django from 5.2.7 to 5.2.8 (#17967)

* bump django from 5.2.7 to 5.2.8

* longer urls



* add debug statements

* Remove debug statements

* import MAX_URL_LENGTH constant from django.http.response

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-11-06 15:50:16 +01:00
authentik-automation[bot]
9f8fd6eabe website/docs: remove broken info box and fix sentence (cherry-pick #17963 to version-2025.10) (#17965)
webiste/docs: remove broken info box and fix sentence (#17963)

Remove broken info box and fix sentence.

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-11-05 15:29:14 +00:00
authentik-automation[bot]
35fb55da15 website/docs: added Note about email_verified scope mapping is set to false by default (cherry-pick #17942 to version-2025.10) (#17961)
website/docs: added Note about email_verified scope mapping is set to false by default (#17942)

* added Note about email_verified set to false

* Update website/docs/add-secure-apps/providers/property-mappings/index.md




* edits

* more edits

* Update website/docs/add-secure-apps/providers/property-mappings/index.md




---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-05 06:58:29 -06:00
authentik-automation[bot]
b1d571a5af tasks/schedules: fix rel obj not being associated or updated (cherry-pick #17934 to version-2025.10) (#17936)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix rel obj not being associated or updated (#17934)
2025-11-04 15:45:01 +01:00
authentik-automation[bot]
fb589592b5 brands: sort matched brand by match length (cherry-pick #17920 to version-2025.10) (#17935)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-11-04 14:41:42 +01:00
authentik-automation[bot]
6468bb5707 brands: add more matching tests (cherry-pick #16185 to version-2025.10) (#17924)
brands: add more matching tests (#16185)

* brands: reproduce matching error



* try some things



* fix tests



* fix tests



* Update authentik/brands/tests.py




* fix tests again?



* wip



---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-03 21:29:33 +00:00
authentik-automation[bot]
70406664dc release: 2025.10.1 2025-11-03 16:42:08 +00:00
authentik-automation[bot]
c58c194180 website/docs: 2025.10.1 release notes (cherry-pick #17918 to version-2025.10) (#17919)
website/docs: 2025.10.1 release notes (#17918)

* website/docs: 2025.10.1 release notes



* Apply suggestions from code review




* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-11-03 17:05:18 +01:00
authentik-automation[bot]
fad87741e7 providers/oauth2: fix kid always required for federation (cherry-pick #17914 to version-2025.10) (#17917)
providers/oauth2: fix kid always required for federation (#17914)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-03 16:26:31 +01:00
authentik-automation[bot]
f6679895e5 providers/radius: revert fix inverted message authenticator validation (#17855) (cherry-pick #17915 to version-2025.10) (#17916)
providers/radius: revert fix inverted message authenticator validation (#17855) (#17915)

Revert "providers/radius: fix inverted message authenticator validation (#17855)"

This reverts commit 09e3301c8f.

Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-03 16:26:17 +01:00
authentik-automation[bot]
a573a72ecb providers/radius: fix inverted message authenticator validation (cherry-pick #17855 to version-2025.10) (#17888)
providers/radius: fix inverted message authenticator validation (#17855)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-11-01 18:28:06 +01:00
authentik-automation[bot]
b72709ebbc web/a11y: User library -- fix issues surrounding element focus, ARIA labeling. (cherry-pick #17522 to version-2025.10) (#17828)
web/a11y: User library -- fix issues surrounding element focus, ARIA labeling. (#17522)

* web/a11y: Fix issues surrounding element focus, aria labeling.

* web: Fix focus

* web: Fix nested focus

* web: Fix menu visibility when anchor positioning is not supported.

* web: Fix icon fallback behavior, labels.

* web: Fix flickering, descriptions.

* web: Fix excess width on mobile.

* web: Fix rendering artifacts on mobile.

* web: Remove aria-controls behavior.

- This is buggy, similar to aria-owns, and may cause crashes.

* web: Fix tabpanel focus attempting to scroll page.

* web: Fix issues surrounding consistent tab panel parameter testing.

* web: add shared helpers.

* web: Tidy comments.

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-01 17:05:19 +01:00
authentik-automation[bot]
449742fbc0 web: Consistent Tab Panel URL Parameters (cherry-pick #17804 to version-2025.10) (#17859)
web: Consistent Tab Panel URL Parameters (#17804)

* web: Fix tabpanel focus attempting to scroll page.

* web: Fix issues surrounding consistent tab panel parameter testing.

* web: add shared helpers.

* web: Tidy comments.

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
2025-11-01 17:04:43 +01:00
authentik-automation[bot]
1b02cc0dae internal: full openssl path (cherry-pick #17856 to version-2025.10) (#17860) 2025-10-31 15:40:51 +01:00
authentik-automation[bot]
b0945ee7e9 outpost: revert breaking signals change (cherry-pick #17847 to version-2025.10) (#17848)
outpost: revert breaking signals change (#17847)

I have no idea why this breaks tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-31 02:20:17 +01:00
authentik-automation[bot]
6682136af1 outposts: update permissions more eagerly (cherry-pick #17783 to version-2025.10) (#17841)
outposts: update permissions more eagerly (#17783)

* wip

* wip

* a

* a



* rm

* this

* rm test files

* cover one more case



---------

Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-10-31 00:33:54 +01:00
authentik-automation[bot]
24cb5ae4c1 tasks: sanitize log attributes (cherry-pick #17833 to version-2025.10) (#17842)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-30 19:03:13 +01:00
authentik-automation[bot]
9e272c7121 core: bump astral-sh/uv from 0.9.5 to 0.9.6 (cherry-pick #17820 to version-2025.10) (#17835)
core: bump astral-sh/uv from 0.9.5 to 0.9.6 (#17820)

Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.5...0.9.6)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.9.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-30 18:05:56 +01:00
authentik-automation[bot]
5dc7b7cdae web/admin: fix scim provider form (cherry-pick #17831 to version-2025.10) (#17834)
web/admin: fix scim provider form (#17831)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-30 17:52:38 +01:00
authentik-automation[bot]
2e2c52e49c internal/web/proxy: fix return status code during startup (cherry-pick #17827 to version-2025.10) (#17832)
internal/web/proxy: fix return status code during startup (#17827)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-30 17:37:03 +01:00
Jens L.
38f1ef0506 ci: rework internal repo (#17797) (#17829)
* ci: rework internal repo



* also fix retention workflow



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-10-30 17:32:03 +01:00
authentik-automation[bot]
3517562549 internal: fix go deprecation for +build (cherry-pick #17806 to version-2025.10) (#17824)
internal: fix go deprecation for +build (#17806)

Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-30 15:48:50 +01:00
authentik-automation[bot]
cdbe40143d root: use hashes for dockerfile FROM (cherry-pick #17795 to version-2025.10) (#17798)
* Cherry-pick #17795 to version-2025.10 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #17795
Original commit: 6f35c32190

* fix conflict

Signed-off-by: Jens L. <jens@goauthentik.io>

---------

Signed-off-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-29 14:01:28 +01:00
authentik-automation[bot]
5816f0d17c tasks: delay startup signals (cherry-pick #17769 to version-2025.10) (#17775)
tasks: delay startup signals (#17769)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-28 18:15:23 +00:00
authentik-automation[bot]
907ea8b2e9 packages/django-postgres-cache: use upsert instead of select/update in a transaction (cherry-pick #17760 to version-2025.10) (#17767)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-28 16:26:14 +01:00
authentik-automation[bot]
b38af89960 providers/oauth2: move encryption key field (cherry-pick #17722 to version-2025.10) (#17729)
providers/oauth2: move encryption key field (#17722)

it is often mis configured

closes #17678

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-28 16:14:11 +01:00
authentik-automation[bot]
d52db187bf providers/radius: fix panic when no cert is configured (cherry-pick #17762 to version-2025.10) (#17766)
providers/radius: fix panic when no cert is configured (#17762)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-28 16:12:21 +01:00
authentik-automation[bot]
2093e0e63f sources/oauth: Make PKCE verifier 128 characters (cherry-pick #17763 to version-2025.10) (#17765)
Co-authored-by: Alex Whitehead-Smith <alex.me.smith@gmail.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-10-28 16:07:13 +01:00
authentik-automation[bot]
2791d87ceb providers/proxy: fix missing JWT/claims header (cherry-pick #17759 to version-2025.10) (#17764)
providers/proxy: fix missing JWT/claims header (#17759)

* replace interface{} with any



* fix raw token not saved to map or json



* also fix proxy claims



* fix test



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-28 15:48:25 +01:00
authentik-automation[bot]
fdc3d95b59 root: Add Dockerfile label org.opencontainers.image.source (cherry-pick #17756 to version-2025.10) (#17757)
root: Add Dockerfile label org.opencontainers.image.source (#17756)

Add label source in dockerfiles

Co-authored-by: Erwan Hervé <62173453+Erwan-loot@users.noreply.github.com>
2025-10-28 13:48:44 +01:00
authentik-automation[bot]
de7a61cee0 website/docs: fix placeholder leftover (cherry-pick #17737 to version-2025.10) (#17738)
website/docs: fix placeholder leftover (#17737)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-27 21:31:46 +01:00
authentik-automation[bot]
f2805b9b8a release: 2025.10.0 2025-10-27 19:35:16 +00:00
authentik-automation[bot]
f48a91fbf4 website/docs: finalise 2025.10 release notes (cherry-pick #17728 to version-2025.10) (#17733)
website/docs: finalise 2025.10 release notes (#17728)

* website/docs: finalise 2025.10 release notes



* format



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-27 19:01:01 +00:00
authentik-automation[bot]
f056c0808d website/docs: update flow context ref (cherry-pick #17723 to version-2025.10) (#17732)
website/docs: update flow context ref (#17723)

* website/docs: update flow context ref



* format



* Update website/docs/add-secure-apps/flows-stages/flow/context/index.mdx




* Update website/docs/add-secure-apps/flows-stages/flow/context/index.mdx




---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-10-27 19:39:09 +01:00
authentik-automation[bot]
06a6d45139 enterprise: handle cached naive timezone (cherry-pick #17695 to version-2025.10) (#17730)
enterprise: handle cached naive timezone (#17695)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-27 19:13:52 +01:00
authentik-automation[bot]
0e12642f12 website/docs: blueprints: add a bit more info (cherry-pick #17704 to version-2025.10) (#17708)
website/docs: blueprints: add a bit more info (#17704)

* website/docs: blueprints: add a bit more info

* this might be worth mentioning

* fix

* a bit more info

Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-26 14:18:03 +00:00
authentik-automation[bot]
01406d364e website/docs: add short-lived certificate recommendation (cherry-pick #17628 to version-2025.10) (#17633)
website/docs: add short-lived certificate recommendation (#17628)

Add certificate recommendation

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-10-25 02:32:38 +00:00
authentik-automation[bot]
b9b16dba59 website/docs: release notes: Add Zot integration (cherry-pick #17700 to version-2025.10) (#17701)
Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-25 01:03:48 +00:00
authentik-automation[bot]
1ef83f3295 website/docs: eap add info about custom validation (cherry-pick #17642 to version-2025.10) (#17699)
website/docs: eap add info about custom validation (#17642)

* add info about custom validation

* tweaked table

* remove bullet

* remove other bullet

---------

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-10-24 21:07:58 +00:00
authentik-automation[bot]
343506d104 website/docs: add note about invite link not bound (cherry-pick #17657 to version-2025.10) (#17672)
website/docs: add note about invite link not bound (#17657)

* invite link not bound

* marcelo's truth

* jens tweak

---------

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
2025-10-24 11:43:32 -05:00
authentik-automation[bot]
aeb4e1057e providers/proxy: drop headers with underscores (cherry-pick #17650 to version-2025.10) (#17651)
providers/proxy: drop headers with underscores (#17650)

drop any headers with underscores that we set in the remote system

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-22 16:28:52 +02:00
authentik-automation[bot]
0bcd1c268c website/docs: rel notes 2025.10: add 3 more integration guides (cherry-pick #17641 to version-2025.10) (#17652)
website/docs: rel notes 2025.10: add 3 more integration guides (#17641)

* add 3 more int guides

* Apply suggestion from @dominic-r



* is github's suggestion thingy usually this buggy

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-22 13:48:02 +00:00
authentik-automation[bot]
ecba1ffe94 enterprise: add prometheus metrics for license usage and expiry (cherry-pick #17606 to version-2025.10) (#17637)
enterprise: add prometheus metrics for license usage and expiry (#17606)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-21 18:55:14 +02:00
authentik-automation[bot]
b7d303936c release: 2025.10.0-rc3 2025-10-21 13:21:18 +00:00
authentik-automation[bot]
c1bc2a4565 ci: use forked release action to deal with large release notes (cherry-pick #17625 to version-2025.10) (#17626)
ci: use forked release action to deal with large release notes (#17625)

* ci: use forked release action to deal with large release notes



* bump build



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-21 14:39:57 +02:00
authentik-automation[bot]
1422c3aff3 core, web: update translations (cherry-pick #17605 to version-2025.10) (#17627)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-10-21 14:26:37 +02:00
authentik-automation[bot]
d4a77583ea website: fix active menu link background overlap (cherry-pick #17607 to version-2025.10) (#17620)
website: fix active menu link background overlap (#17607)

Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-21 07:12:41 -04:00
authentik-automation[bot]
78d270bf25 release: 2025.10.0-rc2 2025-10-21 00:19:36 +00:00
authentik-automation[bot]
6d1c7f90e2 release: 2025.10.0-rc1 2025-10-20 23:43:29 +00:00
344 changed files with 7295 additions and 3370 deletions

View File

@@ -57,7 +57,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
cd web && npm i
cd web && npm ci
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}

View File

@@ -142,7 +142,9 @@ updates:
labels:
- dependencies
- package-ecosystem: docker
directory: "/"
directories:
- /
- /website
schedule:
interval: daily
time: "04:00"

View File

@@ -67,21 +67,20 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: make empty clients
if: ${{ inputs.release }}
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Setup node
if: ${{ !inputs.release }}
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: generate ts client
if: ${{ !inputs.release }}
run: make gen-client-ts
- name: Setup go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v5
with:
go-version-file: "go.mod"
- name: Generate API Clients
run: |
make gen-client-ts
make gen-client-go
- name: Build Docker Image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
id: push

View File

@@ -15,7 +15,6 @@ permissions:
jobs:
build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@@ -13,7 +13,6 @@ env:
jobs:
publish-source-docs:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
timeout-minutes: 120
steps:

View File

@@ -61,7 +61,6 @@ jobs:
working-directory: website/
run: npm run build -w integrations
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
permissions:
# Needed to upload container images to ghcr.io
@@ -121,4 +120,3 @@ jobs:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: ${{ github.repository == 'goauthentik/authentik-internal' && 'build-container' || '[]' }}

View File

@@ -9,7 +9,6 @@ on:
jobs:
test-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false

View File

@@ -80,7 +80,15 @@ jobs:
cp authentik/lib/default.yml local.env.yml
cp -R .github ..
cp -R scripts ..
git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
# Previous stable tag
prev_stable=$(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
# Current version family based on
current_version_family=$(python -c "from authentik import VERSION; print(VERSION)" | grep -vE -- 'rc[0-9]+$')
if [[ -n $current_version_family ]]; then
prev_stable=$current_version_family
fi
echo "::notice::Checking out ${prev_stable} as stable version..."
git checkout $(prev_stable)
rm -rf .github/ scripts/
mv ../.github ../scripts .
- name: Setup authentik env (stable)

View File

@@ -67,7 +67,6 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
build-container:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
timeout-minutes: 120
needs:
- ci-outpost-mark

View File

@@ -13,7 +13,6 @@ env:
jobs:
build:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@@ -5,10 +5,13 @@ on:
# schedule:
# - cron: "0 0 * * *" # every day at midnight
workflow_dispatch:
inputs:
dry-run:
type: boolean
description: Enable dry-run mode
jobs:
clean-ghcr:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
name: Delete old unused container images
runs-on: ubuntu-latest
steps:
@@ -18,12 +21,12 @@ jobs:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Delete 'dev' containers older than a week
uses: snok/container-retention-policy@3b0972b2276b171b212f8c4efbca59ebba26eceb # v2
uses: snok/container-retention-policy@3b0972b2276b171b212f8c4efbca59ebba26eceb # v3.0.1
with:
image-names: dev-server,dev-ldap,dev-proxy
image-tags: "!gh-next,!gh-main"
cut-off: One week ago UTC
account-type: org
org-name: goauthentik
untagged-only: false
account: goauthentik
tag-selection: untagged
token: ${{ steps.generate_token.outputs.token }}
skip-tags: gh-next,gh-main
dry-run: ${{ inputs.dry-run }}

View File

@@ -19,7 +19,6 @@ permissions:
jobs:
publish:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false

View File

@@ -12,7 +12,6 @@ permissions:
jobs:
update-next:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
environment: internal-production
steps:

View File

@@ -84,9 +84,14 @@ jobs:
- rac
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v5
with:
go-version-file: "go.mod"
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5
with:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
@@ -98,10 +103,10 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
- name: make empty clients
- name: Generate API Clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
make gen-client-ts
make gen-client-go
- name: Docker Login Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
@@ -155,10 +160,17 @@ jobs:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Build web
- name: Install web dependencies
working-directory: web/
run: |
npm ci
- name: Generate API Clients
run: |
make gen-client-ts
make gen-client-go
- name: Build web
working-directory: web/
run: |
npm run build-proxy
- name: Build outpost
run: |

View File

@@ -47,8 +47,14 @@ jobs:
test:
name: Pre-release test
runs-on: ubuntu-latest
needs:
- check-inputs
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: "version-${{ needs.check-inputs.outputs.major_version }}"
- name: Setup authentik env
uses: ./.github/actions/setup
- run: make test-docker
bump-authentik:
name: Bump authentik version
@@ -83,11 +89,12 @@ jobs:
# ID from https://api.github.com/users/authentik-automation[bot]
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git pull
git commit -a -m "release: ${{ inputs.version }}" --allow-empty
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
- name: Create Release
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
token: "${{ steps.app-token.outputs.token }}"
tag_name: "version/${{ inputs.version }}"

View File

@@ -1,22 +0,0 @@
---
name: Repo - Cleanup internal mirror
on:
workflow_dispatch:
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb # 5cf300935bc2e068f73ea69bcc411a8a997208eb
with:
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force --prune
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@@ -1,21 +0,0 @@
---
name: Repo - Mirror to internal
on: [push, delete]
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@5cf300935bc2e068f73ea69bcc411a8a997208eb # 5cf300935bc2e068f73ea69bcc411a8a997208eb
with:
target_repo_url: git@github.com:goauthentik/authentik-internal.git
ssh_private_key: ${{ secrets.GH_MIRROR_KEY }}
args: --tags --force
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

@@ -12,7 +12,6 @@ permissions:
jobs:
stale:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@@ -17,7 +17,6 @@ env:
jobs:
compile:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- id: generate_token

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-slim AS node-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-trixie-slim@sha256:45babd1b4ce0349fb12c4e24bf017b90b96d52806db32e001e3013f341bef0fe AS node-builder
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
@@ -26,7 +26,7 @@ RUN npm run build && \
npm run build:sfe
# Stage 2: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25.3-bookworm AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25.3-trixie@sha256:7534a6264850325fcce93e47b87a0e3fddd96b308440245e6ab1325fa8a44c91 AS go-builder
ARG TARGETOS
ARG TARGETARCH
@@ -44,6 +44,7 @@ RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/v
RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
--mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \
--mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \
--mount=type=cache,target=/go/pkg/mod \
go mod download
@@ -57,13 +58,14 @@ COPY ./go.mod /go/src/goauthentik.io/go.mod
COPY ./go.sum /go/src/goauthentik.io/go.sum
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \
go build -o /go/authentik ./cmd/server
# Stage 3: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.1 AS geoip
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.1@sha256:faecdca22579730ab0b7dea5aa9af350bb3c93cb9d39845c173639ead30346d2 AS geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
ENV GEOIPUPDATE_VERBOSE="1"
@@ -76,9 +78,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.9.4 AS uv
FROM ghcr.io/astral-sh/uv:0.9.6@sha256:4b96ee9429583983fd172c33a02ecac5242d63fb46bc27804748e38c1cc9ad0d AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips AS python-base
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips@sha256:700fc8c1e290bd14e5eaca50b1d8e8c748c820010559cbfb4c4f8dfbe2c4c9ff AS python-base
ENV VENV_PATH="/ak-root/.venv" \
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
@@ -139,6 +141,7 @@ ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \

View File

@@ -120,11 +120,11 @@ bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
$(eval current_version := $(shell cat ${PWD}/internal/constants/VERSION))
sed -i 's/^version = ".*"/version = "$(version)"/' pyproject.toml
sed -i 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
$(MAKE) gen-build gen-compose aws-cfn
npm version --no-git-tag-version --allow-same-version $(version)
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
sed -i "s/\"${current_version}\"/\"$(version)\"/" ${PWD}/package.json ${PWD}/package-lock.json ${PWD}/web/package.json ${PWD}/web/package-lock.json
echo -n $(version) > ${PWD}/internal/constants/VERSION
#########################
@@ -189,23 +189,15 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
gen-client-py: gen-clean-py ## Build and install the authentik API for Python
mkdir -p ${PWD}/${GEN_API_PY}
ifeq ($(wildcard ${PWD}/${GEN_API_PY}/.*),)
git clone --depth 1 https://github.com/goauthentik/client-python.git ${PWD}/${GEN_API_PY}
else
cd ${PWD}/${GEN_API_PY} && git pull
endif
cp ${PWD}/schema.yml ${PWD}/${GEN_API_PY}
make -C ${PWD}/${GEN_API_PY} build version=${NPM_VERSION}
gen-client-go: gen-clean-go ## Build and install the authentik API for Golang
mkdir -p ${PWD}/${GEN_API_GO}
ifeq ($(wildcard ${PWD}/${GEN_API_GO}/.*),)
git clone --depth 1 https://github.com/goauthentik/client-go.git ${PWD}/${GEN_API_GO}
else
cd ${PWD}/${GEN_API_GO} && git pull
endif
cp ${PWD}/schema.yml ${PWD}/${GEN_API_GO}
make -C ${PWD}/${GEN_API_GO} build
make -C ${PWD}/${GEN_API_GO} build version=${NPM_VERSION}
go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO}
gen-dev-config: ## Generate a local development config file

View File

@@ -3,7 +3,7 @@
from functools import lru_cache
from os import environ
VERSION = "2025.10.0-rc1"
VERSION = "2025.10.4"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@@ -1,8 +1,11 @@
"""Test brands"""
from json import loads
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.blueprints.tests import apply_blueprint
from authentik.brands.api import Themes
from authentik.brands.models import Brand
from authentik.core.models import Application
@@ -23,6 +26,7 @@ class TestBrands(APITestCase):
_flag = flag()
if _flag.visibility == "public":
self.default_flags[_flag.key] = _flag.get()
Brand.objects.all().delete()
def test_current_brand(self):
"""Test Current brand API"""
@@ -44,7 +48,6 @@ class TestBrands(APITestCase):
def test_brand_subdomain(self):
"""Test Current brand API"""
Brand.objects.all().delete()
Brand.objects.create(domain="bar.baz", branding_title="custom")
self.assertJSONEqual(
self.client.get(
@@ -65,7 +68,6 @@ class TestBrands(APITestCase):
def test_fallback(self):
"""Test fallback brand"""
Brand.objects.all().delete()
self.assertJSONEqual(
self.client.get(reverse("authentik_api:brand-current")).content.decode(),
{
@@ -81,6 +83,109 @@ class TestBrands(APITestCase):
},
)
@apply_blueprint("default/default-brand.yaml")
def test_blueprint(self):
"""Test Current brand API"""
response = loads(self.client.get(reverse("authentik_api:brand-current")).content.decode())
response.pop("flow_authentication", None)
response.pop("flow_invalidation", None)
response.pop("flow_user_settings", None)
self.assertEqual(
response,
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "authentik",
"branding_custom_css": "",
"matched_domain": "authentik-default",
"ui_footer_links": [],
"ui_theme": Themes.AUTOMATIC,
"default_locale": "",
"flags": self.default_flags,
},
)
@apply_blueprint("default/default-brand.yaml")
def test_blueprint_with_other_brand(self):
"""Test Current brand API"""
Brand.objects.create(domain="bar.baz", branding_title="custom")
response = loads(self.client.get(reverse("authentik_api:brand-current")).content.decode())
response.pop("flow_authentication", None)
response.pop("flow_invalidation", None)
response.pop("flow_user_settings", None)
self.assertEqual(
response,
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "authentik",
"branding_custom_css": "",
"matched_domain": "authentik-default",
"ui_footer_links": [],
"ui_theme": Themes.AUTOMATIC,
"default_locale": "",
"flags": self.default_flags,
},
)
self.assertJSONEqual(
self.client.get(
reverse("authentik_api:brand-current"), HTTP_HOST="foo.bar.baz"
).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "custom",
"branding_custom_css": "",
"matched_domain": "bar.baz",
"ui_footer_links": [],
"ui_theme": Themes.AUTOMATIC,
"default_locale": "",
"flags": self.default_flags,
},
)
def test_brand_subdomain_same_suffix(self):
"""Test Current brand API"""
Brand.objects.create(domain="bar.baz", branding_title="custom-weak")
Brand.objects.create(domain="foo.bar.baz", branding_title="custom-strong")
self.assertJSONEqual(
self.client.get(
reverse("authentik_api:brand-current"), HTTP_HOST="foo.bar.baz"
).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "custom-strong",
"branding_custom_css": "",
"matched_domain": "foo.bar.baz",
"ui_footer_links": [],
"ui_theme": Themes.AUTOMATIC,
"default_locale": "",
"flags": self.default_flags,
},
)
def test_brand_subdomain_other_suffix(self):
"""Test Current brand API"""
Brand.objects.create(domain="bar.baz", branding_title="custom-weak")
Brand.objects.create(domain="foo.bar.baz", branding_title="custom-strong")
self.assertJSONEqual(
self.client.get(
reverse("authentik_api:brand-current"), HTTP_HOST="other.bar.baz"
).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
"branding_title": "custom-weak",
"branding_custom_css": "",
"matched_domain": "bar.baz",
"ui_footer_links": [],
"ui_theme": Themes.AUTOMATIC,
"default_locale": "",
"flags": self.default_flags,
},
)
def test_create_default_multiple(self):
"""Test attempted creation of multiple default brands"""
Brand.objects.create(

View File

@@ -2,8 +2,8 @@
from typing import Any
from django.db.models import F, Q
from django.db.models import Value as V
from django.db.models import Case, F, IntegerField, Q, Value, When
from django.db.models.functions import Length
from django.http.request import HttpRequest
from django.utils.html import _json_script_escapes
from django.utils.safestring import mark_safe
@@ -19,15 +19,36 @@ DEFAULT_BRAND = Brand(domain="fallback")
def get_brand_for_request(request: HttpRequest) -> Brand:
"""Get brand object for current request"""
db_brands = (
Brand.objects.annotate(host_domain=V(request.get_host()))
.filter(Q(host_domain__iendswith=F("domain")) | _q_default)
.order_by("default")
brand = (
Brand.objects.annotate(
host_domain=Value(request.get_host()),
domain_length=Length("domain"),
match_priority=Case(
When(
condition=Q(host_domain__iendswith=F("domain")),
then=F("domain_length"),
),
default=Value(-1),
output_field=IntegerField(),
),
is_default_fallback=Case(
When(
condition=Q(default=True),
then=Value(0),
),
default=Value(-2),
output_field=IntegerField(),
),
)
.filter(Q(match_priority__gt=-1) | Q(default=True))
.order_by("-match_priority", "-is_default_fallback")
.first()
)
brands = list(db_brands.all())
if len(brands) < 1:
if brand is None:
return DEFAULT_BRAND
return brands[0]
return brand
def context_processor(request: HttpRequest) -> dict[str, Any]:

View File

@@ -4,7 +4,8 @@ from collections.abc import Iterator
from copy import copy
from django.core.cache import cache
from django.db.models import QuerySet
from django.db.models import Case, QuerySet
from django.db.models.expressions import When
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
@@ -23,6 +24,7 @@ from authentik.api.pagination import Pagination
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import Application, User
from authentik.events.logs import LogEventSerializer, capture_logs
@@ -63,9 +65,21 @@ class ApplicationSerializer(ModelSerializer):
def get_launch_url(self, app: Application) -> str | None:
"""Allow formatting of launch URL"""
user = None
user_data = None
if "request" in self.context:
user = self.context["request"].user
return app.get_launch_url(user)
# Cache serialized user data to avoid N+1 when formatting launch URLs
# for multiple applications. UserSerializer accesses user.ak_groups which
# would otherwise trigger a query for each application.
if user is not None:
if "_cached_user_data" not in self.context:
# Prefetch groups to avoid N+1
self.context["_cached_user_data"] = UserSerializer(instance=user).data
user_data = self.context["_cached_user_data"]
return app.get_launch_url(user, user_data=user_data)
def validate_slug(self, slug: str) -> str:
if slug in Application.reserved_slugs:
@@ -158,8 +172,23 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
applications.append(application)
return applications
def _expand_applications(self, applications: list[Application]) -> QuerySet[Application]:
"""
Re-fetch with proper prefetching for serialization
Cached applications don't have prefetched relationships, causing N+1 queries
during serialization when get_provider() is called
"""
if not applications:
return self.get_queryset().none()
pks = [app.pk for app in applications]
return (
self.get_queryset()
.filter(pk__in=pks)
.order_by(Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pks)]))
)
def _filter_applications_with_launch_url(
self, paginated_apps: Iterator[Application]
self, paginated_apps: QuerySet[Application]
) -> list[Application]:
applications = []
for app in paginated_apps:
@@ -262,6 +291,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
except ValueError as exc:
raise ValidationError from exc
allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)
allowed_applications = self._expand_applications(allowed_applications)
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)
@@ -280,6 +311,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
allowed_applications,
timeout=86400,
)
allowed_applications = self._expand_applications(allowed_applications)
if only_with_launch_url == "true":
allowed_applications = self._filter_applications_with_launch_url(allowed_applications)

View File

@@ -18,10 +18,14 @@ from authentik.core.models import Provider
class ProviderSerializer(ModelSerializer, MetaNameSerializer):
"""Provider Serializer"""
assigned_application_slug = ReadOnlyField(source="application.slug")
assigned_application_name = ReadOnlyField(source="application.name")
assigned_backchannel_application_slug = ReadOnlyField(source="backchannel_application.slug")
assigned_backchannel_application_name = ReadOnlyField(source="backchannel_application.name")
assigned_application_slug = ReadOnlyField(source="application.slug", allow_null=True)
assigned_application_name = ReadOnlyField(source="application.name", allow_null=True)
assigned_backchannel_application_slug = ReadOnlyField(
source="backchannel_application.slug", allow_null=True
)
assigned_backchannel_application_name = ReadOnlyField(
source="backchannel_application.name", allow_null=True
)
component = SerializerMethodField()

View File

@@ -8,7 +8,7 @@ from uuid import uuid4
from django.contrib.auth import logout
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
from django.utils.translation import override
@@ -47,7 +47,7 @@ async def aget_user(request):
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
def process_request(self, request: HttpRequest) -> HttpResponseBadRequest | None:
if not hasattr(request, "session"):
raise ImproperlyConfigured(
"The Django authentication middleware requires session "
@@ -62,7 +62,8 @@ class AuthenticationMiddleware(MiddlewareMixin):
user = request.user
if user and user.is_authenticated and not user.is_active:
logout(request)
raise AssertionError()
return HttpResponseBadRequest()
return None
class ImpersonateMiddleware:

View File

@@ -15,7 +15,7 @@ from django.db import models
from django.db.models import Q, QuerySet, options
from django.db.models.constants import LOOKUP_SEP
from django.http import HttpRequest
from django.utils.functional import SimpleLazyObject, cached_property
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_cte import CTE, with_cte
@@ -524,6 +524,10 @@ class ApplicationQuerySet(QuerySet):
qs = self.select_related("provider")
for subclass in Provider.objects.get_queryset()._get_subclasses_recurse(Provider):
qs = qs.select_related(f"provider__{subclass}")
# Also prefetch/select through each subclass path to ensure casted instances have access
qs = qs.prefetch_related(f"provider__{subclass}__property_mappings")
qs = qs.select_related(f"provider__{subclass}__application")
qs = qs.select_related(f"provider__{subclass}__backchannel_application")
return qs
@@ -583,20 +587,28 @@ class Application(SerializerModel, PolicyBindingModel):
return CONFIG.get("web.path", "/")[:-1] + self.meta_icon.name
return self.meta_icon.url
def get_launch_url(self, user: Optional["User"] = None) -> str | None:
"""Get launch URL if set, otherwise attempt to get launch URL based on provider."""
def get_launch_url(
self, user: Optional["User"] = None, user_data: dict | None = None
) -> str | None:
"""Get launch URL if set, otherwise attempt to get launch URL based on provider.
Args:
user: User instance for formatting the URL
user_data: Pre-serialized user data to avoid re-serialization (performance optimization)
"""
from authentik.core.api.users import UserSerializer
url = None
if self.meta_launch_url:
url = self.meta_launch_url
elif provider := self.get_provider():
url = provider.launch_url
if user and url:
if isinstance(user, SimpleLazyObject):
user._setup()
user = user._wrapped
try:
return url % user.__dict__
# Use pre-serialized data if available, otherwise serialize now
if user_data is None:
user_data = UserSerializer(instance=user).data
return url % user_data
except Exception as exc: # noqa
LOGGER.warning("Failed to format launch url", exc=exc)
return url

View File

@@ -1,5 +1,7 @@
"""authentik core signals"""
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth.signals import user_logged_in
from django.core.cache import cache
from django.db.models import Model
@@ -17,6 +19,8 @@ from authentik.core.models import (
User,
default_token_duration,
)
from authentik.flows.apps import RefreshOtherFlowsAfterAuthentication
from authentik.root.ws.consumer import build_device_group
# Arguments: user: User, password: str
password_changed = Signal()
@@ -47,6 +51,16 @@ def user_logged_in_session(sender, request: HttpRequest, user: User, **_):
if session:
session.save()
if not RefreshOtherFlowsAfterAuthentication().get():
return
layer = get_channel_layer()
device_cookie = request.COOKIES.get("authentik_device")
if device_cookie:
async_to_sync(layer.group_send)(
build_device_group(device_cookie),
{"type": "event.session.authenticated"},
)
@receiver(post_delete, sender=AuthenticatedSession)
def authenticated_session_delete(sender: type[Model], instance: "AuthenticatedSession", **_):

View File

@@ -35,8 +35,13 @@ def clean_expired_models():
LOGGER.debug("Expired models", model=cls, amount=amount)
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
clear_expired_cache()
Message.delete_expired()
GroupChannel.delete_expired()
for cls in [Message, GroupChannel]:
objects = cls.objects.all().filter(expires__lt=now())
amount = objects.count()
for obj in chunked_queryset(objects):
obj.delete()
LOGGER.debug("Expired models", model=cls, amount=amount)
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
@actor(description=_("Remove temporary users created by SAML Sources."))

View File

@@ -194,6 +194,8 @@ class TestApplicationsAPI(APITestCase):
"provider_obj": {
"assigned_application_name": "allowed",
"assigned_application_slug": "allowed",
"assigned_backchannel_application_name": None,
"assigned_backchannel_application_slug": None,
"authentication_flow": None,
"invalidation_flow": None,
"authorization_flow": str(self.provider.authorization_flow.pk),
@@ -248,6 +250,8 @@ class TestApplicationsAPI(APITestCase):
"provider_obj": {
"assigned_application_name": "allowed",
"assigned_application_slug": "allowed",
"assigned_backchannel_application_name": None,
"assigned_backchannel_application_slug": None,
"authentication_flow": None,
"invalidation_flow": None,
"authorization_flow": str(self.provider.authorization_flow.pk),

View File

@@ -28,8 +28,8 @@ from authentik.core.views.interface import (
)
from authentik.flows.views.interface import FlowInterfaceView
from authentik.root.asgi_middleware import AuthMiddlewareStack
from authentik.root.messages.consumer import MessageConsumer
from authentik.root.middleware import ChannelsLoggingMiddleware
from authentik.root.ws.consumer import MessageConsumer
from authentik.tenants.channels import TenantsAwareMiddleware
urlpatterns = [

View File

@@ -2,6 +2,8 @@
from binascii import hexlify
from hashlib import md5
from ssl import PEM_FOOTER, PEM_HEADER
from textwrap import wrap
from uuid import uuid4
from cryptography.hazmat.backends import default_backend
@@ -20,6 +22,11 @@ from authentik.lib.models import CreatedUpdatedModel, SerializerModel
LOGGER = get_logger()
def format_cert(raw_pam: str) -> str:
"""Format a PEM certificate that is either missing its header/footer or is in a single line"""
return "\n".join([PEM_HEADER, *wrap(raw_pam.replace("\n", ""), 64), PEM_FOOTER])
def fingerprint_sha256(cert: Certificate) -> str:
"""Get SHA256 Fingerprint of certificate"""
return hexlify(cert.fingerprint(hashes.SHA256()), ":").decode("utf-8")

View File

@@ -1,11 +1,21 @@
"""Enterprise app config"""
from django.conf import settings
from prometheus_client import Gauge
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.utils.time import fqdn_rand
from authentik.tasks.schedules.common import ScheduleSpec
GAUGE_LICENSE_USAGE = Gauge(
"authentik_enterprise_license_usage",
"Enterprise license usage (percentage per user type).",
["user_type"],
)
GAUGE_LICENSE_EXPIRY = Gauge(
"authentik_enterprise_license_expiry_seconds", "Duration until license expires, in seconds."
)
class EnterpriseConfig(ManagedAppConfig):
"""Base app config for all enterprise apps"""

View File

@@ -217,7 +217,7 @@ class LicenseKey:
def summary(self) -> LicenseSummary:
"""Summary of license status"""
status = self.status()
latest_valid = datetime.fromtimestamp(self.exp)
latest_valid = datetime.fromtimestamp(self.exp).replace(tzinfo=UTC)
return LicenseSummary(
latest_valid=latest_valid,
internal_users=self.internal_users,

View File

@@ -42,6 +42,8 @@ def send_ssf_events(
for stream in Stream.objects.filter(**stream_filter):
event_data = stream.prepare_event_payload(event_type, data, **extra_data)
events_data[stream.uuid] = event_data
if not events_data:
return
ssf_events_dispatch.send(events_data)

View File

@@ -1,18 +1,41 @@
"""Enterprise signals"""
from datetime import datetime
from datetime import UTC, datetime
from django.core.cache import cache
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch import receiver
from django.utils.timezone import get_current_timezone
from django.utils.timezone import get_current_timezone, now
from authentik.enterprise.license import CACHE_KEY_ENTERPRISE_LICENSE
from authentik.enterprise.models import License
from authentik.enterprise.apps import GAUGE_LICENSE_EXPIRY, GAUGE_LICENSE_USAGE
from authentik.enterprise.license import CACHE_KEY_ENTERPRISE_LICENSE, LicenseKey
from authentik.enterprise.models import License, LicenseUsageStatus
from authentik.enterprise.tasks import enterprise_update_usage
from authentik.root.monitoring import monitoring_set
from authentik.tasks.schedules.models import Schedule
@receiver(monitoring_set)
def monitoring_set_enterprise(sender, **kwargs):
"""set enterprise gauges"""
summary = LicenseKey.cached_summary()
if summary.status == LicenseUsageStatus.UNLICENSED:
return
percentage_internal = (
0
if summary.internal_users <= 0
else LicenseKey.get_internal_user_count() / (summary.internal_users / 100)
)
percentage_external = (
0
if summary.external_users <= 0
else LicenseKey.get_external_user_count() / (summary.external_users / 100)
)
GAUGE_LICENSE_USAGE.labels(user_type="internal").set(percentage_internal)
GAUGE_LICENSE_USAGE.labels(user_type="external").set(percentage_external)
GAUGE_LICENSE_EXPIRY.set((summary.latest_valid.replace(tzinfo=UTC) - now()).total_seconds())
@receiver(pre_save, sender=License)
def pre_save_license(sender: type[License], instance: License, **_):
"""Extract data from license jwt and save it into model"""

View File

@@ -1,4 +1,5 @@
from binascii import hexlify
from enum import IntFlag, auto
from urllib.parse import unquote_plus
from cryptography.exceptions import InvalidSignature
@@ -17,7 +18,7 @@ from django.utils.translation import gettext_lazy as _
from authentik.brands.models import Brand
from authentik.core.models import User
from authentik.crypto.models import CertificateKeyPair, fingerprint_sha256
from authentik.crypto.models import CertificateKeyPair, fingerprint_sha256, format_cert
from authentik.enterprise.stages.mtls.models import (
CertAttributes,
MutualTLSStage,
@@ -43,14 +44,28 @@ HEADER_OUTPOST_FORWARDED = "X-Authentik-Outpost-Certificate"
PLAN_CONTEXT_CERTIFICATE = "certificate"
class ParseOptions(IntFlag):
# URL unquote the string
UNQUOTE = auto()
# Re-add PEM Header & footer, and chunk it into 64 character lines
FORMAT = auto()
class MTLSStageView(ChallengeStageView):
def __parse_single_cert(self, raw: str | None) -> list[Certificate]:
def __parse_single_cert(self, raw: str | None, *options: ParseOptions) -> list[Certificate]:
"""Helper to parse a single certificate"""
if not raw:
return []
for opt in options:
match opt:
case ParseOptions.FORMAT:
raw = format_cert(raw)
case ParseOptions.UNQUOTE:
raw = unquote_plus(raw)
try:
cert = load_pem_x509_certificate(unquote_plus(raw).encode())
cert = load_pem_x509_certificate(raw.encode())
return [cert]
except ValueError as exc:
self.logger.info("Failed to parse certificate", exc=exc)
@@ -59,6 +74,7 @@ class MTLSStageView(ChallengeStageView):
def _parse_cert_xfcc(self) -> list[Certificate]:
"""Parse certificates in the format given to us in
the format of the authentik router/envoy"""
# https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
xfcc_raw = self.request.headers.get(HEADER_PROXY_FORWARDED)
if not xfcc_raw:
return []
@@ -68,18 +84,26 @@ class MTLSStageView(ChallengeStageView):
raw_cert = {k.split("=")[0]: k.split("=")[1] for k in el}
if "Cert" not in raw_cert:
continue
certs.extend(self.__parse_single_cert(raw_cert["Cert"]))
certs.extend(self.__parse_single_cert(raw_cert["Cert"], ParseOptions.UNQUOTE))
return certs
def _parse_cert_nginx(self) -> list[Certificate]:
"""Parse certificates in the format nginx-ingress gives to us"""
# https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication
# https://github.com/kubernetes/ingress-nginx/blob/78f593b24494a0674b362faf551079f06d71b5a9/rootfs/etc/nginx/template/nginx.tmpl#L1096
sslcc_raw = self.request.headers.get(HEADER_NGINX_FORWARDED)
return self.__parse_single_cert(sslcc_raw)
return self.__parse_single_cert(sslcc_raw, ParseOptions.UNQUOTE)
def _parse_cert_traefik(self) -> list[Certificate]:
"""Parse certificates in the format traefik gives to us"""
# https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/passtlsclientcert/
ftcc_raw = self.request.headers.get(HEADER_TRAEFIK_FORWARDED)
return self.__parse_single_cert(ftcc_raw)
if not ftcc_raw:
return []
certs = []
for cert in ftcc_raw.split(","):
certs.extend(self.__parse_single_cert(cert, ParseOptions.UNQUOTE, ParseOptions.FORMAT))
return certs
def _parse_cert_outpost(self) -> list[Certificate]:
"""Parse certificates in the format outposts give to us. Also authenticates
@@ -92,7 +116,7 @@ class MTLSStageView(ChallengeStageView):
) and not user.has_perm("authentik_stages_mtls.pass_outpost_certificate"):
return []
outpost_raw = self.request.headers.get(HEADER_OUTPOST_FORWARDED)
return self.__parse_single_cert(outpost_raw)
return self.__parse_single_cert(outpost_raw, ParseOptions.UNQUOTE)
def get_authorities(self) -> list[CertificateKeyPair] | None:
# We can't access `certificate_authorities` on `self.executor.current_stage`, as that would

View File

@@ -1,3 +1,4 @@
from ssl import PEM_FOOTER, PEM_HEADER
from unittest.mock import MagicMock, patch
from urllib.parse import quote_plus
@@ -51,6 +52,10 @@ class MTLSStageTests(FlowTestCase):
User.objects.filter(username="client").delete()
self.cert_user = create_test_user(username="client")
def _format_traefik(self, cert: str | None = None):
cert = cert if cert else self.client_cert
return quote_plus(cert.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", ""))
def test_parse_xfcc(self):
"""Test authentik Proxy/Envoy's XFCC format"""
with self.assertFlowFinishes() as plan:
@@ -78,7 +83,7 @@ class MTLSStageTests(FlowTestCase):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))
@@ -138,7 +143,9 @@ class MTLSStageTests(FlowTestCase):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(cert.certificate_data)},
headers={
"X-Forwarded-TLS-Client-Cert": self._format_traefik(cert.certificate_data)
},
)
self.assertEqual(res.status_code, 200)
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
@@ -149,7 +156,7 @@ class MTLSStageTests(FlowTestCase):
User.objects.filter(username="client").delete()
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
@@ -163,7 +170,7 @@ class MTLSStageTests(FlowTestCase):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))
@@ -176,7 +183,7 @@ class MTLSStageTests(FlowTestCase):
self.stage.save()
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))
@@ -187,7 +194,7 @@ class MTLSStageTests(FlowTestCase):
self.stage.save()
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageResponse(res, self.flow, component="ak-stage-access-denied")
@@ -209,7 +216,7 @@ class MTLSStageTests(FlowTestCase):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
headers={"X-Forwarded-TLS-Client-Cert": quote_plus(self.client_cert)},
headers={"X-Forwarded-TLS-Client-Cert": self._format_traefik()},
)
self.assertEqual(res.status_code, 200)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))

View File

@@ -0,0 +1,49 @@
"""Enterprise metrics tests"""
from unittest.mock import MagicMock, patch
from django.test import TestCase
from prometheus_client import REGISTRY
from authentik.core.models import User
from authentik.core.tests.utils import create_test_user
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import License
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.lib.generators import generate_id
from authentik.root.monitoring import monitoring_set
class TestEnterpriseMetrics(TestCase):
"""Enterprise metrics tests"""
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
def test_usage_empty(self):
"""Test usage (no users)"""
License.objects.create(key=generate_id())
User.objects.all().delete()
create_test_user()
monitoring_set.send_robust(self)
self.assertEqual(
REGISTRY.get_sample_value(
"authentik_enterprise_license_usage", {"user_type": "internal"}
),
1.0,
)
self.assertEqual(
REGISTRY.get_sample_value(
"authentik_enterprise_license_usage", {"user_type": "external"}
),
0,
)

View File

@@ -1,7 +1,7 @@
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from datetime import UTC, datetime
from typing import Any
from django.utils.timezone import now
@@ -28,7 +28,7 @@ class LogEvent:
def from_event_dict(item: EventDict) -> "LogEvent":
event = item.pop("event")
log_level = item.pop("level").lower()
timestamp = datetime.fromisoformat(item.pop("timestamp"))
timestamp = datetime.fromisoformat(item.pop("timestamp")).replace(tzinfo=UTC)
item.pop("pid", None)
# Sometimes log entries have both `level` and `log_level` set, but `level` is always set
item.pop("log_level", None)

View File

@@ -4,6 +4,7 @@ from prometheus_client import Gauge, Histogram
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.utils.reflection import all_subclasses
from authentik.tenants.flags import Flag
GAUGE_FLOWS_CACHED = Gauge(
"authentik_flows_cached",
@@ -22,6 +23,12 @@ HIST_FLOWS_PLAN_TIME = Histogram(
)
class RefreshOtherFlowsAfterAuthentication(Flag[bool], key="flows_refresh_others"):
default = False
visibility = "public"
class AuthentikFlowsConfig(ManagedAppConfig):
"""authentik flows app config"""

View File

@@ -145,7 +145,6 @@ worker:
consumer_listen_timeout: "seconds=30"
task_max_retries: 5
task_default_time_limit: "minutes=10"
lock_purge_interval: "minutes=1"
task_purge_interval: "days=1"
task_expiration: "days=30"
scheduler_interval: "seconds=60"

View File

@@ -41,7 +41,7 @@ ARG_SANITIZE = re.compile(r"[:.-]")
def sanitize_arg(arg_name: str) -> str:
return re.sub(ARG_SANITIZE, "_", arg_name)
return re.sub(ARG_SANITIZE, "_", slugify(arg_name))
class BaseEvaluator:
@@ -299,7 +299,9 @@ class BaseEvaluator:
def wrap_expression(self, expression: str) -> str:
"""Wrap expression in a function, call it, and save the result as `result`"""
handler_signature = ",".join(sanitize_arg(x) for x in self._context.keys())
handler_signature = ",".join(
[x for x in [sanitize_arg(x) for x in self._context.keys()] if x]
)
full_expression = ""
full_expression += f"def handler({handler_signature}):\n"
full_expression += indent(expression, " ")

View File

@@ -28,6 +28,8 @@ def register_signals(
# This primarily happens during user login
if sender == User and update_fields == {"last_login"}:
return
if not provider_type.objects.exists():
return
task_sync_direct_dispatch.send(
class_to_path(instance.__class__),
instance.pk,
@@ -39,6 +41,8 @@ def register_signals(
def model_pre_delete(sender: type[Model], instance: User | Group, **_):
"""Pre-delete handler"""
if not provider_type.objects.exists():
return
task_sync_direct_dispatch.send(
class_to_path(instance.__class__),
instance.pk,
@@ -54,6 +58,8 @@ def register_signals(
"""Sync group membership"""
if action not in ["post_add", "post_remove"]:
return
if not provider_type.objects.exists():
return
task_sync_m2m_dispatch.send(instance.pk, action, list(pk_set), reverse)
m2m_changed.connect(model_m2m_changed, User.ak_groups.through, dispatch_uid=uid, weak=False)

View File

@@ -1,5 +1,6 @@
"""Test Evaluator base functions"""
from pathlib import Path
from unittest.mock import patch
from django.test import RequestFactory, TestCase
@@ -239,3 +240,18 @@ class TestEvaluator(TestCase):
with self.assertRaises(ValueError) as cm:
evaluator.evaluate("return ak_send_email(123, 'Test', body='Body')")
self.assertIn("Address must be a string or list of strings", str(cm.exception))
def test_expr_arg_escape(self):
"""Test escaping of arguments"""
eval = BaseEvaluator()
eval._context = {
'z=getattr(getattr(__import__("os"), "popen")("id > /tmp/test"), "read")()': "bar",
"@@": "baz",
"{{": "baz",
"aa@@": "baz",
}
res = eval.evaluate("return locals()")
self.assertEqual(
res, {"zgetattrgetattr__import__os_popenid_tmptest_read": "bar", "aa": "baz"}
)
self.assertFalse(Path("/tmp/test").exists()) # nosec

View File

@@ -1,16 +1,17 @@
"""authentik database utilities"""
import gc
from collections.abc import Generator
from django.db import reset_queries
from django.db.models import QuerySet
from django.db.models import Model, QuerySet
def chunked_queryset(queryset: QuerySet, chunk_size: int = 1_000):
def chunked_queryset[T: Model](queryset: QuerySet[T], chunk_size: int = 1_000) -> Generator[T]:
if not queryset.exists():
return []
def get_chunks(qs: QuerySet):
def get_chunks(qs: QuerySet) -> Generator[QuerySet[T]]:
qs = qs.order_by("pk")
pks = qs.values_list("pk", flat=True)
start_pk = pks[0]

View File

@@ -47,7 +47,9 @@ class OutpostSerializer(ModelSerializer):
)
providers_obj = ProviderSerializer(source="providers", many=True, read_only=True)
service_connection_obj = ServiceConnectionSerializer(
source="service_connection", read_only=True
source="service_connection",
read_only=True,
allow_null=True,
)
refresh_interval_s = SerializerMethodField()

View File

@@ -203,6 +203,12 @@ class DockerController(BaseController):
"labels": self._get_labels(),
"restart_policy": {"Name": "unless-stopped"},
"network": self.outpost.config.docker_network,
"healthcheck": {
"test": ["CMD", f"/{self.outpost.type}", "healthcheck"],
"interval": 5 * 1_000 * 1_000_000,
"retries": 20,
"start_period": 3 * 1_000 * 1_000_000,
},
}
if self.outpost.config.docker_map_ports:
container_args["ports"] = {

View File

@@ -49,6 +49,9 @@ def outpost_m2m_changed(sender, instance: Outpost | Provider, action: str, **_):
if action not in ["post_add", "post_remove", "post_clear"]:
return
if isinstance(instance, Outpost):
# Rebuild permissions when providers change
LOGGER.debug("Rebuilding outpost service account permissions", outpost=instance)
instance.build_user_permissions(instance.user)
outpost_controller.send_with_options(
args=(instance.pk,),
rel_obj=instance.service_connection,
@@ -92,6 +95,15 @@ def outpost_post_save(sender, instance: Outpost, created: bool, **_):
def outpost_related_post_save(sender, instance: OutpostServiceConnection | OutpostModel, **_):
for outpost in instance.outpost_set.all():
# Rebuild permissions in case provider's required objects changed
if isinstance(instance, OutpostModel):
LOGGER.info(
"Provider changed, rebuilding permissions and sending update",
outpost=outpost.name,
provider=instance.name if hasattr(instance, "name") else str(instance),
)
outpost.build_user_permissions(outpost.user)
LOGGER.debug("Sending update to outpost", outpost=outpost.name, trigger="provider_change")
outpost_send_update.send_with_options(
args=(outpost.pk,),
rel_obj=outpost,

View File

@@ -386,11 +386,18 @@ class OAuth2Provider(WebfingerProvider, Provider):
def __str__(self):
return f"OAuth2 Provider {self.name}"
def encode(self, payload: dict[str, Any]) -> str:
"""Represent the ID Token as a JSON Web Token (JWT)."""
def encode(self, payload: dict[str, Any], jwt_type: str | None = None) -> str:
"""Represent the ID Token as a JSON Web Token (JWT).
:param payload The payload to encode into the JWT
:param jwt_type The type of the JWT. This will be put in the JWT header using the `typ`
parameter. See RFC7515 Section 4.1.9. If not set fallback to the default of `JWT`.
"""
headers = {}
if self.signing_key:
headers["kid"] = self.signing_key.kid
if jwt_type is not None:
headers["typ"] = jwt_type
key, alg = self.jwt_key
encoded = encode(payload, key, algorithm=alg, headers=headers)
if self.encryption_key:

View File

@@ -109,7 +109,7 @@ def user_session_deleted_oauth_backchannel_logout_and_tokens_removal(
"""Revoke tokens upon user logout"""
LOGGER.debug("Sending back-channel logout notifications signal!", session=instance)
access_tokens = AccessToken.objects.filter(
access_tokens = AccessToken.objects.select_related("provider").filter(
user=instance.user,
session__session__session_key=instance.session.session_key,
)
@@ -128,7 +128,8 @@ def user_session_deleted_oauth_backchannel_logout_and_tokens_removal(
and token.provider.logout_method == OAuth2LogoutMethod.BACKCHANNEL
]
backchannel_logout_notification_dispatch.send(revocations=backchannel_tokens)
if backchannel_tokens:
backchannel_logout_notification_dispatch.send(revocations=backchannel_tokens)
access_tokens.delete()

View File

@@ -113,11 +113,16 @@ class TestBackChannelLogout(OAuthTestCase):
def _decode_token(self, token, provider=None):
"""Helper to decode and validate a JWT token"""
decoded = self._decode_token_complete(token, provider)
return decoded["payload"]
def _decode_token_complete(self, token, provider=None):
"""Helper to decode and validate a JWT token into a header, and payload dict"""
provider = provider or self.provider
key, alg = provider.jwt_key
if alg != "HS256":
key = provider.signing_key.public_key
return jwt.decode(
return jwt.decode_complete(
token, key, algorithms=[alg], options={"verify_exp": False, "verify_aud": False}
)
@@ -155,6 +160,16 @@ class TestBackChannelLogout(OAuthTestCase):
self.assertEqual(decoded3["sub"], sub)
self.assertIn("events", decoded3)
def test_create_logout_token_header_type(self):
"""Test creating logout tokens and checking if the token header type is correct"""
session_id = "test-session-123"
token1 = self._create_logout_token(session_id=session_id)
decoded = self._decode_token_complete(token1)
self.assertIsNotNone(decoded["header"])
self.assertEqual(decoded["header"]["typ"], "logout+jwt")
@patch("authentik.providers.oauth2.tasks.get_http_session")
def test_send_backchannel_logout_request_scenarios(self, mock_get_session):
"""Test various scenarios for backchannel logout request task"""

View File

@@ -126,6 +126,30 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
},
)
def test_deactivate(self):
"""test deactivated user"""
self.user.is_active = False
self.user.save()
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": SCOPE_OPENID,
"client_id": self.provider.client_id,
"username": "sa",
"password": self.token.key,
},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{
"error": "invalid_grant",
"error_description": TokenError.errors["invalid_grant"],
"request_id": response.headers["X-authentik-id"],
},
)
def test_permission_denied(self):
"""test permission denied"""
group = Group.objects.create(name="foo")

View File

@@ -5,6 +5,7 @@ import uuid
from base64 import b64decode, urlsafe_b64encode
from binascii import Error
from hashlib import sha256
from hmac import compare_digest
from typing import Any
from urllib.parse import urlparse
@@ -206,7 +207,9 @@ def authenticate_provider(request: HttpRequest) -> OAuth2Provider | None:
provider, client_id, client_secret = provider_from_request(request)
if not provider:
return None
if client_id != provider.client_id or client_secret != provider.client_secret:
if not compare_digest(client_id, provider.client_id) or not compare_digest(
client_secret, provider.client_secret
):
LOGGER.debug("(basic) Provider for basic auth does not exist")
return None
CTX_AUTH_VIA.set("oauth_client_secret")
@@ -259,4 +262,4 @@ def create_logout_token(
if session_key:
payload["sid"] = hash_session_key(session_key)
# Encode the token
return provider.encode(payload)
return provider.encode(payload, jwt_type="logout+jwt")

View File

@@ -4,6 +4,7 @@ from base64 import b64decode
from binascii import Error
from dataclasses import InitVar, dataclass
from datetime import datetime
from hmac import compare_digest
from re import error as RegexError
from re import fullmatch
from typing import Any
@@ -161,9 +162,8 @@ class TokenParams:
def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest):
if self.grant_type in [GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN]:
if (
self.provider.client_type == ClientTypes.CONFIDENTIAL
and self.provider.client_secret != self.client_secret
if self.provider.client_type == ClientTypes.CONFIDENTIAL and not compare_digest(
self.provider.client_secret, self.client_secret
):
LOGGER.warning(
"Invalid client secret",
@@ -336,7 +336,7 @@ class TokenParams:
self, request: HttpRequest, username: str, password: str
):
# Authenticate user based on credentials
user = User.objects.filter(username=username).first()
user = User.objects.filter(username=username, is_active=True).first()
if not user:
raise TokenError("invalid_grant")
token: Token = Token.filter_not_expired(
@@ -378,9 +378,11 @@ class TokenParams:
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to parse JWT for kid lookup", exc=exc)
raise TokenError("invalid_grant") from None
expected_kid = decode_unvalidated["header"]["kid"]
fallback_alg = decode_unvalidated["header"]["alg"]
expected_kid = decode_unvalidated["header"].get("kid")
fallback_alg = decode_unvalidated["header"].get("alg")
token = source = None
if not expected_kid or not fallback_alg:
return None, None
for source in self.provider.jwt_federation_sources.filter(
oidc_jwks__keys__contains=[{"kid": expected_kid}]
):

View File

@@ -75,6 +75,8 @@ class TestEndpointsAPI(APITestCase):
"component": "ak-provider-rac-form",
"assigned_application_slug": self.app.slug,
"assigned_application_name": self.app.name,
"assigned_backchannel_application_name": None,
"assigned_backchannel_application_slug": None,
"verbose_name": "RAC Provider",
"verbose_name_plural": "RAC Providers",
"meta_model_name": "authentik_providers_rac.racprovider",
@@ -126,6 +128,8 @@ class TestEndpointsAPI(APITestCase):
"component": "ak-provider-rac-form",
"assigned_application_slug": self.app.slug,
"assigned_application_name": self.app.name,
"assigned_backchannel_application_name": None,
"assigned_backchannel_application_slug": None,
"connection_expiry": "hours=8",
"delete_token_on_disconnect": False,
"verbose_name": "RAC Provider",
@@ -155,6 +159,8 @@ class TestEndpointsAPI(APITestCase):
"component": "ak-provider-rac-form",
"assigned_application_slug": self.app.slug,
"assigned_application_name": self.app.name,
"assigned_backchannel_application_name": None,
"assigned_backchannel_application_slug": None,
"connection_expiry": "hours=8",
"delete_token_on_disconnect": False,
"verbose_name": "RAC Provider",

View File

@@ -9,10 +9,9 @@ from defusedxml.lxml import fromstring
from lxml import etree # nosec
from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair
from authentik.crypto.models import CertificateKeyPair, format_cert
from authentik.flows.models import Flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
NS_MAP,
@@ -24,18 +23,6 @@ from authentik.sources.saml.processors.constants import (
LOGGER = get_logger()
def format_pem_certificate(unformatted_cert: str) -> str:
"""Format single, inline certificate into PEM Format"""
# Ensure that all linebreaks are gone
unformatted_cert = unformatted_cert.replace("\n", "")
chunks, chunk_size = len(unformatted_cert), 64
lines = [PEM_HEADER]
for i in range(0, chunks, chunk_size):
lines.append(unformatted_cert[i : i + chunk_size])
lines.append(PEM_FOOTER)
return "\n".join(lines)
@dataclass(slots=True)
class ServiceProviderMetadata:
"""SP Metadata Dataclass"""
@@ -87,7 +74,7 @@ class ServiceProviderMetadataParser:
)
if len(signing_certs) < 1:
return None
raw_cert = format_pem_certificate(signing_certs[0])
raw_cert = format_cert(signing_certs[0])
# sanity check, make sure the certificate is valid.
load_pem_x509_certificate(raw_cert.encode("utf-8"), default_backend())
return CertificateKeyPair(

View File

@@ -2,9 +2,7 @@
import base64
import zlib
PEM_HEADER = "-----BEGIN CERTIFICATE-----"
PEM_FOOTER = "-----END CERTIFICATE-----"
from ssl import PEM_FOOTER, PEM_HEADER
def decode_base64_and_inflate(encoded: str, encoding="utf-8") -> str:

View File

@@ -1,12 +1,15 @@
"""Group client"""
from itertools import batched
from typing import Any
from django.db import transaction
from orjson import dumps
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from authentik.core.models import Group
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.base import Direction
from authentik.lib.sync.outgoing.exceptions import (
@@ -113,10 +116,23 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
self._patch_add_users(connection, users)
return connection
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a group is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group"""
scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id
payload = scim_group.model_dump(mode="json", exclude_unset=True)
if not self.diff(payload, connection):
self.logger.debug("Skipping group write as data has not changed")
return self.patch_compare_users(group)
try:
if self._config.patch.supported:
return self._update_patch(group, scim_group, connection)

View File

@@ -83,7 +83,7 @@ class EnterpriseUser(BaseModel):
class User(BaseUser):
"""Modified User schema with added externalId field"""
model_config = ConfigDict(serialize_by_alias=True)
model_config = ConfigDict(serialize_by_alias=True, extra="allow")
id: str | int | None = None
schemas: list[str] = [SCIM_USER_SCHEMA]
@@ -106,6 +106,8 @@ class User(BaseUser):
class Group(BaseGroup):
"""Modified Group schema with added externalId field"""
model_config = ConfigDict(extra="allow")
id: str | int | None = None
schemas: list[str] = [SCIM_GROUP_SCHEMA]
externalId: str | None = None

View File

@@ -1,10 +1,14 @@
"""User client"""
from typing import Any
from django.db import transaction
from django.utils.http import urlencode
from orjson import dumps
from pydantic import ValidationError
from authentik.core.models import User
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import ObjectExistsSyncException, StopSync
from authentik.policies.utils import delete_none_values
@@ -92,17 +96,30 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
provider=self.provider, user=user, scim_id=scim_id, attributes=response
)
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a user is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, user: User, connection: SCIMProviderUser):
"""Update existing user"""
scim_user = self.to_schema(user, connection)
scim_user.id = connection.scim_id
payload = scim_user.model_dump(
mode="json",
exclude_unset=True,
)
if not self.diff(payload, connection):
self.logger.debug("Skipping user write as data has not changed")
return
response = self._request(
"PUT",
f"/Users/{connection.scim_id}",
json=scim_user.model_dump(
mode="json",
exclude_unset=True,
),
json=payload,
)
connection.attributes = response
connection.save()

View File

@@ -9,7 +9,7 @@ from requests_mock import Mocker
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderGroup
class SCIMGroupTests(TestCase):
@@ -106,6 +106,7 @@ class SCIMGroupTests(TestCase):
"displayName": group.name,
},
)
group.name = generate_id()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -148,3 +149,56 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[3].method, "DELETE")
self.assertEqual(mock.request_history[3].url, f"https://localhost/Groups/{scim_id}")
@Mocker()
def test_group_create_update_noop(self, mock: Mocker):
"""Test group creation and update"""
scim_id = generate_id()
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
uid = generate_id()
group = Group.objects.create(
name=uid,
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
with open("schemas/scim-group.schema.json", encoding="utf-8") as schema:
validate(body, loads(schema.read()))
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
conn = SCIMProviderGroup.objects.filter(group=group).first()
conn.attributes = {
"id": scim_id,
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
}
conn.save()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
self.assertEqual(mock.request_history[2].method, "GET")

View File

@@ -10,7 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.lib.sync.outgoing.base import SAFE_METHODS
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderUser
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
from authentik.tasks.models import Task
from authentik.tenants.models import Tenant
@@ -95,7 +95,12 @@ class SCIMUserTests(TestCase):
"""Test user creation with custom schema"""
schema = SCIMMapping.objects.create(
name="custom_schema",
expression="""return {"schemas": ["foo"]}""",
expression="""return {
"schemas": ["urn:ietf:params:scim:schemas:extension:slack:profile:2.0:User"],
"urn:ietf:params:scim:schemas:extension:slack:profile:2.0:User": {
"startDate": "2024-04-10T00:00:00+0000",
},
}""",
)
self.provider.property_mappings.add(schema)
scim_id = generate_id()
@@ -121,7 +126,10 @@ class SCIMUserTests(TestCase):
self.assertJSONEqual(
mock.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User", "foo"],
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:slack:profile:2.0:User",
],
"active": True,
"emails": [
{
@@ -138,6 +146,9 @@ class SCIMUserTests(TestCase):
},
"displayName": f"{uid} {uid}",
"userName": uid,
"urn:ietf:params:scim:schemas:extension:slack:profile:2.0:User": {
"startDate": "2024-04-10T00:00:00+0000",
},
},
)
@@ -262,6 +273,8 @@ class SCIMUserTests(TestCase):
"userName": uid,
},
)
# Update user
user.name = "foo bar"
user.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -444,3 +457,85 @@ class SCIMUserTests(TestCase):
self.assertIsNotNone(log.attributes["url"])
self.assertIsNotNone(log.attributes["body"])
self.assertIsNotNone(log.attributes["method"])
@Mocker()
def test_user_create_update_noop(self, mock: Mocker):
"""Test user creation and update"""
scim_id = generate_id()
mock: Mocker
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Users",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Users",
json={
"id": scim_id,
},
)
uid = generate_id()
user = User.objects.create(
username=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
},
)
conn = SCIMProviderUser.objects.filter(user=user).first()
conn.attributes = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
"id": scim_id,
}
conn.save()
user.save()
self.assertEqual(mock.call_count, 3)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
# No PUT request

View File

@@ -2,6 +2,7 @@
from django.contrib import messages
from django.contrib.auth import login
from django.db import transaction
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
@@ -16,11 +17,16 @@ class UseTokenView(View):
def get(self, request: HttpRequest, key: str) -> HttpResponse:
"""Check if token exists, log user in and delete token."""
tokens = Token.filter_not_expired(key=key, intent=TokenIntents.INTENT_RECOVERY)
if not tokens.exists():
raise Http404
token = tokens.first()
login(request, token.user, backend=BACKEND_INBUILT)
token.delete()
with transaction.atomic():
tokens = (
Token.filter_not_expired(key=key, intent=TokenIntents.INTENT_RECOVERY)
.select_for_update()
.select_related("user")
)
token = tokens.first()
if token is None:
raise Http404
login(request, token.user, backend=BACKEND_INBUILT)
token.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("authentik_core:if-user")

View File

@@ -1,27 +0,0 @@
"""websocket Message consumer"""
from channels.generic.websocket import JsonWebsocketConsumer
from django.core.cache import cache
from authentik.root.messages.storage import CACHE_PREFIX
class MessageConsumer(JsonWebsocketConsumer):
"""Consumer which sends django.contrib.messages Messages over WS.
channel_name is saved into cache with user_id, and when a add_message is called"""
session_key: str
def connect(self):
self.accept()
self.session_key = self.scope["session"].session_key
if not self.session_key:
return
cache.set(f"{CACHE_PREFIX}{self.session_key}_messages_{self.channel_name}", True, None)
def disconnect(self, code):
cache.delete(f"{CACHE_PREFIX}{self.session_key}_messages_{self.channel_name}")
def event_update(self, event: dict):
"""Event handler which is called by Messages Storage backend"""
self.send_json(event)

View File

@@ -6,6 +6,7 @@ from hashlib import sha512
from pathlib import Path
import orjson
from django.utils import http as utils_http
from sentry_sdk import set_tag
from xmlsec import enable_debug_trace
@@ -248,7 +249,7 @@ SESSION_COOKIE_AGE = timedelta_from_string(
).total_seconds()
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
MESSAGE_STORAGE = "authentik.root.ws.storage.ChannelsStorage"
MIDDLEWARE_FIRST = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
@@ -379,9 +380,6 @@ DRAMATIQ = {
"broker_class": "authentik.tasks.broker.Broker",
"channel_prefix": "authentik",
"task_model": "authentik.tasks.models.Task",
"lock_purge_interval": timedelta_from_string(
CONFIG.get("worker.lock_purge_interval")
).total_seconds(),
"task_purge_interval": timedelta_from_string(
CONFIG.get("worker.task_purge_interval")
).total_seconds(),
@@ -429,6 +427,7 @@ DRAMATIQ = {
},
),
("dramatiq.results.middleware.Results", {"store_results": True}),
("authentik.tasks.middleware.StartupSignalsMiddleware", {}),
("authentik.tasks.middleware.CurrentTask", {}),
("authentik.tasks.middleware.TenantMiddleware", {}),
("authentik.tasks.middleware.ModelDataMiddleware", {}),
@@ -471,6 +470,12 @@ STORAGES = {
},
}
# Django 5.2.8 and CVE-2025-64458 added a strong enforcement of 2048 characters
# as the maximum for a URL to redirect to, mostly for running on windows.
# However our URLs can easily exceed that with OAuth/SAML Query parameters or hash values
# 8192 should cover most cases..
utils_http.MAX_URL_LENGTH = utils_http.MAX_URL_LENGTH * 4
# Media files
if CONFIG.get("storage.media.backend", "file") == "s3":

View File

@@ -0,0 +1,57 @@
"""websocket Message consumer"""
from hashlib import sha256
from asgiref.sync import async_to_sync
from channels.generic.websocket import JsonWebsocketConsumer
from django.core.cache import cache
from django.db import connection
from authentik.root.ws.storage import CACHE_PREFIX
def build_session_group(session_key: str):
return sha256(
f"{connection.schema_name}/group_client_session_{str(session_key)}".encode()
).hexdigest()
def build_device_group(session_key: str):
return sha256(
f"{connection.schema_name}/group_client_device_{str(session_key)}".encode()
).hexdigest()
class MessageConsumer(JsonWebsocketConsumer):
"""Consumer which sends django.contrib.messages Messages over WS.
channel_name is saved into cache with user_id, and when a add_message is called"""
session_key: str
device_cookie: str | None = None
def connect(self):
self.accept()
self.session_key = self.scope["session"].session_key
if self.session_key:
cache.set(f"{CACHE_PREFIX}{self.session_key}_messages_{self.channel_name}", True, None)
if device_cookie := self.scope["cookies"].get("authentik_device", None):
self.device_cookie = device_cookie
async_to_sync(self.channel_layer.group_add)(
build_device_group(self.device_cookie), self.channel_name
)
def disconnect(self, code):
if self.session_key:
cache.delete(f"{CACHE_PREFIX}{self.session_key}_messages_{self.channel_name}")
if self.device_cookie:
async_to_sync(self.channel_layer.group_discard)(
build_device_group(self.device_cookie), self.channel_name
)
def event_message(self, event: dict):
"""Event handler which is called by Messages Storage backend"""
self.send_json(event)
def event_session_authenticated(self, event: dict):
"""Event handler post user authentication"""
self.send_json({"message_type": "session.authenticated"})

View File

@@ -31,7 +31,7 @@ class ChannelsStorage(SessionStorage):
async_to_sync(self.channel.send)(
uid,
{
"type": "event.update",
"type": "event.message",
"message_type": "message",
"level": message.level_tag,
"tags": message.tags,

View File

@@ -298,6 +298,16 @@ class LDAPSource(ScheduledModel, Source):
side_effect=pglock.Return,
)
def get_ldap_server_info(self, srv: Server) -> dict[str, str]:
info = {
"vendor": _("N/A"),
"version": _("N/A"),
}
if srv.info:
info["vendor"] = str(flatten(srv.info.vendor_name))
info["version"] = str(flatten(srv.info.vendor_version))
return info
def check_connection(self) -> dict[str, dict[str, str]]:
"""Check LDAP Connection"""
servers = self.server()
@@ -308,9 +318,8 @@ class LDAPSource(ScheduledModel, Source):
try:
conn = self.connection(server=server)
server_info[server.host] = {
"vendor": str(flatten(conn.server.info.vendor_name)),
"version": str(flatten(conn.server.info.vendor_version)),
"status": "ok",
**self.get_ldap_server_info(conn.server),
}
except LDAPException as exc:
server_info[server.host] = {
@@ -320,9 +329,8 @@ class LDAPSource(ScheduledModel, Source):
try:
conn = self.connection()
server_info["__all__"] = {
"vendor": str(flatten(conn.server.info.vendor_name)),
"version": str(flatten(conn.server.info.vendor_version)),
"status": "ok",
**self.get_ldap_server_info(conn.server),
}
except LDAPException as exc:
server_info["__all__"] = {

View File

@@ -143,7 +143,7 @@ class OAuth2Client(BaseOAuthClient):
if self.source.source_type.urls_customizable and self.source.pkce:
pkce_mode = self.source.pkce
if pkce_mode != PKCEMethod.NONE:
verifier = generate_id()
verifier = generate_id(length=128)
self.request.session[SESSION_KEY_OAUTH_PKCE] = verifier
# https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
if pkce_mode == PKCEMethod.PLAIN:

View File

@@ -205,6 +205,7 @@ class TestOAuthSource(APITestCase):
session = self.client.session
state = session[f"oauth-client-{self.source.name}-request-state"]
verifier = session[SESSION_KEY_OAUTH_PKCE]
self.assertEqual(len(verifier), 128)
challenge = pkce_s256_challenge(verifier)
self.assertEqual(qs["redirect_uri"], ["http://testserver/source/oauth/callback/test/"])

View File

@@ -7,6 +7,7 @@ from django.http import HttpRequest
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from lxml.etree import _Element # nosec
from rest_framework.serializers import Serializer
from authentik.core.models import (
@@ -217,9 +218,8 @@ class SAMLSource(Source):
def property_mapping_type(self) -> type[PropertyMapping]:
return SAMLSourcePropertyMapping
def get_base_user_properties(self, root: Any, name_id: Any, **kwargs):
def get_base_user_properties(self, root: _Element, assertion: _Element, name_id: Any, **kwargs):
attributes = {}
assertion = root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
if assertion is None:
raise ValueError("Assertion element not found")
attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")

View File

@@ -63,6 +63,8 @@ class ResponseProcessor:
_http_request: HttpRequest
_assertion: "Element | None" = None
def __init__(self, source: SAMLSource, request: HttpRequest):
self._source = source
self._http_request = request
@@ -113,6 +115,7 @@ class ResponseProcessor:
index_of,
decrypted_assertion,
)
self._assertion = decrypted_assertion
def _verify_signed(self):
"""Verify SAML Response's Signature"""
@@ -154,6 +157,10 @@ class ResponseProcessor:
raise InvalidSignature() from exc
LOGGER.debug("Successfully verified signature")
parent = signature_node.getparent()
if parent is not None and parent.tag == f"{{{NS_SAML_ASSERTION}}}Assertion":
self._assertion = parent
def _verify_request_id(self):
if self._source.allow_idp_initiated:
# If IdP-initiated SSO flows are enabled, we want to cache the Response ID
@@ -217,14 +224,21 @@ class ResponseProcessor:
identifier=str(name_id.text),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
},
policy_context={},
)
def get_assertion(self) -> "Element | None":
"""Get assertion element, if we have a signed assertion"""
if self._assertion is not None:
return self._assertion
return self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
def _get_name_id(self) -> "Element":
"""Get NameID Element"""
assertion = self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
assertion = self.get_assertion()
if assertion is None:
raise ValueError("Assertion element not found")
subject = assertion.find(f"{{{NS_SAML_ASSERTION}}}Subject")
@@ -277,6 +291,7 @@ class ResponseProcessor:
identifier=str(name_id.text),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
},
policy_context={

View File

@@ -0,0 +1,68 @@
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_other_id_pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">bad</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">bad</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">bad</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>zNDuGxwP4gVkv/Dzt7kiKo/4gzk=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>GLP/vE8uxerB0uDpPslUgLPBL6ePQB619MoQ0I2Y5lAtFE6CB1zh8BnzChRx/bFjNy4byfOe8mFfM0r7WUi1PJOFWyUPoatdLl7wHHBIRTnPpYmu3Tb2Gz0sOP0F8wW7JkBft5gJfVw49nk5si9/3Q3o52jnJZ7dPtqfIOh8uNeopikK0HLF6sU05qCCtjcXfniEnLQFNBFMo9uY5GQqmR5n3nqPz1wYyyfFOAbVmGgBIoO2PfGX2GVLQhltc9qf2JMhks4jgZsZ8iLUIiH1lcLGWZEEs94k8k0P6gSv1uZ7Vbhksd/N9Jq9pCVuEJ/jRPcAdVjzbxqKQAj6ELwr8O6fepTzA+CAdwEolBnx/C6TmSbVZ+IWk6QUGe4x4+IAukC+0hkKENlO0ELOScksvyhpgHbxNA4rp+DhGupCaO/I2RrsQkmvavbqm+wSEspK7scK112SDunjDvqPHsPYgukD33T/97PxTLorg2kKP9HHJwPJKoXXeyOGcA6vwK+RqrAlZ2dLGAgcXo+sJcdCLuvxDNz9VXofBjBZIKVKdmYhm0QJaPYHtuQsAyFavQhdOBOmGHb7QX3YE3Xy4dX4LymtT+Jlb1I4FJSht/9HUIHW1FdhfDak4f7gUgjuMamMddLD0jVgeESupSREzFv/gj2IrctkbgjAO0iuuiBgKMg=</ds:SignatureValue>
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIFUzCCAzugAwIBAgIRAL6tbNcE9Ej9gNlbGKswfFMwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAwwSYXV0aGVudGlrIDIwMjUuNi4zMB4XDTI1MDcxNTE4MDQzNloXDTI2MDcxNjE4MDQzNlowVjEqMCgGA1UEAwwhYXV0aGVudGlrIFNlbGYtc2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQKDAlhdXRoZW50aWsxFDASBgNVBAsMC1NlbGYtc2lnbmVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjmut/+bBRLlyrbf+WIfg8ZTw9t6VnsiU1n04nPTulpRAz4nBOoOHNRIruSpZyFeFa6x9jwn4Ma5EFUH7HqnRvhoujm8U17OglXWZt0DLCZ6S5xPmdMogFXjJDmg9okIcI/cb9VbR6I8uvm1oiaOWCr36RTiqZ6rmdjQcuUPLr1+V/LxWQI463S+5QA2HZxAGalp45MJAz2sa9iczktKMgyYlfjj1cruFARxxeheu5qIK7aQWfyPj1QlMb9mi4VQaxUwGrAui4Tq614ivRJY2SkZb0Aq/LLSQoQWYHtYyQIasrOXJm0JuPDqhINPBDowyhu8DihC3uzOpmTXLKc5UoIQk+Q1h5iH74A3/kxOJUw13FXzRiDxC/yGthPYLyFHsDiJolscMKSCqlDvEMcpM4mxFeud9sKUb71SZr8sqmJl3qtvZmKpkR4y8pN2c00p10t0htqONmr5kyPxmhz0HCrosiPYB4olNjaydKviNTtPJ7TtnPyeA3iXGzCP1e80XzUoJrDqON5/GcpYgqsP/kGj8Qvqesa4Fez+1+5pAGHN2VzQbkHAgK3s4YRXrGLTs7wg27F9T0RE28Mm0RYBkYpdp4/5PuTTulthB9mkUBSJMgENmQAYkapvonFDsJkTi39qnsddbZusOLT4z3hsA38eFEwRqnbNZVUGPIp/O1SsCAwEAAaNVMFMwUQYDVR0RAQH/BEcwRYJDRUZ4QXVLRzV6SlVUTWpWNTJoMkRJMUQ5MXdLblZKaXFwNmpwRTRTTy5zZWxmLXNpZ25lZC5nb2F1dGhlbnRpay5pbzANBgkqhkiG9w0BAQsFAAOCAgEAYLThxDVpA1OIAVK/buueRJExIWr6y4s6NtpuR8UQEcfq5hfoc4zMFGHR5+u1WFIb5siK25xh/OnS7bLdLic6AkjZSrx91+0v2Jn9gfUqbs5AJ040XzAAdx/Mb4s0+537yhB+/JXPylR1QxhGbO7koXQ5JDhAXWKCw2O1C+80mN8dbhQvDkEtsXrHrtXclcqf2TT89XAzc5HAC8NmP4SF+FafAREQB1KdaG4QAbc/gnjsX2YJD89SDL+3jMp6F7R1Ym+bWt5oWqx2tkm6HGXd3fbpfQlnfrRN60tMjjLmw1cDMhOhpdragY5zokniEUL2pKVtrxFp7V1ZpoMI0Kt5MKkOXrezi542NWSgkGehlsDLD9wtuCNem2arR0mNnMLdYkMG7G0dpAq3Tl32dgfMfyKnNyE2O/6/EeEuzUH2NfTU1p7AUQfLrf4rtNcJEs9OAPuC9vy7w9YEpF997T+FhR2Ub1C423NQj4bwlS/9f7MIBkSi1EgnQuiSGB5epxAKI3oOVrmzOpTuvr6wZXV9pM3zdfbcoGuFWP6Ix7W8G5vg+0WvoSjc2fwGXYlidEK3xlQSMAaQ4CMClpPsKLScRq1nrQGzPYoiL1DYubsOWx9ohll6+jNjKI6f79WwbHYrW4EeRIOz38+m46EDjAWZBMgrE7J/3DhgeLEVJYBA5K0=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>

View File

@@ -37,7 +37,9 @@ class TestPropertyMappings(TestCase):
def test_user_base_properties(self):
"""Test user base properties"""
properties = self.source.get_base_user_properties(root=ROOT, name_id=NAME_ID)
properties = self.source.get_base_user_properties(
root=ROOT, assertion=ROOT.find(f"{{{NS_SAML_ASSERTION}}}Assertion"), name_id=NAME_ID
)
self.assertEqual(
properties,
{
@@ -50,7 +52,11 @@ class TestPropertyMappings(TestCase):
def test_group_base_properties(self):
"""Test group base properties"""
properties = self.source.get_base_user_properties(root=ROOT_GROUPS, name_id=NAME_ID)
properties = self.source.get_base_user_properties(
root=ROOT_GROUPS,
assertion=ROOT_GROUPS.find(f"{{{NS_SAML_ASSERTION}}}Assertion"),
name_id=NAME_ID,
)
self.assertEqual(properties["groups"], ["group 1", "group 2"])
for group_id in ["group 1", "group 2"]:
properties = self.source.get_base_group_properties(root=ROOT, group_id=group_id)

View File

@@ -152,6 +152,31 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
def test_verification_assertion_duplicate(self):
"""Test verifying signature inside assertion, where the response has another assertion
before our signed assertion"""
key = load_fixture("fixtures/signature_cert.pem")
kp = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data=key,
)
self.source.verification_kp = kp
self.source.signed_assertion = True
self.source.signed_response = False
request = self.factory.post(
"/",
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_signed_assertion_dup.xml").encode()
).decode()
},
)
parser = ResponseProcessor(self.source, request)
parser.parse()
self.assertNotEqual(parser._get_name_id().text, "bad")
self.assertEqual(parser._get_name_id().text, "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
def test_verification_response(self):
"""Test verifying signature inside response"""
key = load_fixture("fixtures/signature_cert.pem")

View File

@@ -11,10 +11,10 @@ from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
INVITATION_TOKEN_KEY_CONTEXT = "token" # nosec
INVITATION_TOKEN_KEY = "itoken" # nosec
INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
QS_INVITATION_TOKEN_KEY = "itoken" # nosec
PLAN_CONTEXT_INVITATION_TOKEN = "token" # nosec
PLAN_CONTEXT_INVITATION_IN_EFFECT = "invitation_in_effect"
PLAN_CONTEXT_INVITATION = "invitation"
class InvitationStageView(StageView):
@@ -23,13 +23,13 @@ class InvitationStageView(StageView):
def get_token(self) -> str | None:
"""Get token from saved get-arguments or prompt_data"""
# Check for ?token= and ?itoken=
if INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY_CONTEXT in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY_CONTEXT]
if QS_INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][QS_INVITATION_TOKEN_KEY]
if PLAN_CONTEXT_INVITATION_TOKEN in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][PLAN_CONTEXT_INVITATION_TOKEN]
# Check for {'token': ''} in the context
if INVITATION_TOKEN_KEY_CONTEXT in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY_CONTEXT]
if PLAN_CONTEXT_INVITATION_TOKEN in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][PLAN_CONTEXT_INVITATION_TOKEN]
return None
def get_invite(self) -> Invitation | None:
@@ -38,7 +38,7 @@ class InvitationStageView(StageView):
if not token:
return None
try:
invite: Invitation = Invitation.objects.filter(pk=token).first()
invite: Invitation | None = Invitation.filter_not_expired(pk=token).first()
except ValidationError:
self.logger.debug("invalid invitation", token=token)
return None
@@ -60,8 +60,8 @@ class InvitationStageView(StageView):
return self.executor.stage_ok()
return self.executor.stage_invalid(_("Invalid invite/invite not found"))
self.executor.plan.context[INVITATION_IN_EFFECT] = True
self.executor.plan.context[INVITATION] = invite
self.executor.plan.context[PLAN_CONTEXT_INVITATION_IN_EFFECT] = True
self.executor.plan.context[PLAN_CONTEXT_INVITATION] = invite
context = {}
always_merger.merge(context, self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}))

View File

@@ -1,9 +1,11 @@
"""invitation tests"""
from datetime import timedelta
from unittest.mock import MagicMock, patch
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from rest_framework.test import APITestCase
@@ -16,9 +18,9 @@ from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.stage import (
INVITATION_TOKEN_KEY,
INVITATION_TOKEN_KEY_CONTEXT,
PLAN_CONTEXT_INVITATION_TOKEN,
PLAN_CONTEXT_PROMPT,
QS_INVITATION_TOKEN_KEY,
)
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@@ -77,6 +79,31 @@ class TestInvitationStage(FlowTestCase):
self.stage.continue_flow_without_invitation = False
self.stage.save()
def test_with_invitation_expired(self):
"""Test with invitation, expired"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
data = {"foo": "bar"}
invite = Invitation.objects.create(
created_by=get_anonymous_user(),
fixed_data=data,
expires=now() - timedelta(hours=1),
)
base_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
args = urlencode({QS_INVITATION_TOKEN_KEY: invite.pk.hex})
response = self.client.get(base_url + f"?query={args}")
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow=self.flow,
component="ak-stage-access-denied",
)
def test_with_invitation_get(self):
"""Test with invitation, check data in session"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
@@ -89,7 +116,7 @@ class TestInvitationStage(FlowTestCase):
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
base_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
args = urlencode({INVITATION_TOKEN_KEY: invite.pk.hex})
args = urlencode({QS_INVITATION_TOKEN_KEY: invite.pk.hex})
response = self.client.get(base_url + f"?query={args}")
session = self.client.session
@@ -114,7 +141,7 @@ class TestInvitationStage(FlowTestCase):
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
base_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
args = urlencode({INVITATION_TOKEN_KEY: invite.pk.hex})
args = urlencode({QS_INVITATION_TOKEN_KEY: invite.pk.hex})
response = self.client.get(base_url + f"?query={args}")
session = self.client.session
@@ -134,7 +161,7 @@ class TestInvitationStage(FlowTestCase):
)
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY_CONTEXT: invite.pk.hex}
plan.context[PLAN_CONTEXT_PROMPT] = {PLAN_CONTEXT_INVITATION_TOKEN: invite.pk.hex}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View File

@@ -261,7 +261,9 @@ class Prompt(SerializerModel):
return value
def field(self, default: Any | None, choices: list[Any] | None = None) -> CharField:
def field( # noqa PLR0915
self, default: Any | None, choices: list[Any] | None = None
) -> CharField:
"""Get field type for Challenge and response. Choices are only valid for CHOICE_FIELDS."""
field_class = CharField
kwargs = {
@@ -275,6 +277,7 @@ class Prompt(SerializerModel):
field_class = ReadOnlyField
# required can't be set for ReadOnlyField
kwargs["required"] = False
kwargs["allow_blank"] = True
case FieldTypes.EMAIL:
field_class = EmailField
kwargs["allow_blank"] = not self.required
@@ -306,7 +309,14 @@ class Prompt(SerializerModel):
if self.type in CHOICE_FIELDS:
field_class = ChoiceField
kwargs["choices"] = choices or []
kwargs["choices"] = []
if choices:
for choice in choices:
label, value = choice, choice
if isinstance(choice, dict):
label = choice.get("label", "")
value = choice.get("value", "")
kwargs["choices"].append((value, label))
if default:
kwargs["default"] = default

View File

@@ -23,6 +23,7 @@ from authentik import authentik_full_version
from authentik.events.models import Event, EventAction
from authentik.lib.sentry import should_ignore_exception
from authentik.lib.utils.reflection import class_to_path
from authentik.root.signals import post_startup, pre_startup, startup
from authentik.tasks.models import Task, TaskLog, TaskStatus, WorkerStatus
from authentik.tenants.models import Tenant
from authentik.tenants.utils import get_current_tenant
@@ -32,6 +33,14 @@ HEALTHCHECK_LOGGER = get_logger("authentik.worker").bind()
DB_ERRORS = (OperationalError, Error)
class StartupSignalsMiddleware(Middleware):
def after_process_boot(self, broker: Broker):
_startup_sender = type("WorkerStartup", (object,), {})
pre_startup.send(sender=_startup_sender)
startup.send(sender=_startup_sender)
post_startup.send(sender=_startup_sender)
class CurrentTask(BaseCurrentTask):
@classmethod
def get_task(cls) -> Task:

View File

@@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from django_dramatiq_postgres.models import TaskBase, TaskState
from authentik.events.logs import LogEvent
from authentik.events.utils import sanitize_item
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_dict
from authentik.tenants.models import Tenant
@@ -174,7 +175,7 @@ class TaskLog(models.Model):
log_level=log_event.log_level,
logger=log_event.logger,
timestamp=log_event.timestamp,
attributes=log_event.attributes,
attributes=sanitize_item(log_event.attributes),
)
@classmethod
@@ -193,7 +194,7 @@ class TaskLog(models.Model):
log_level=log_event.log_level,
logger=log_event.logger,
timestamp=log_event.timestamp,
attributes=log_event.attributes,
attributes=sanitize_item(log_event.attributes),
)
for log_event in log_events
]

View File

@@ -2,9 +2,10 @@ import pickle # nosec
from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
from uuid import UUID
from dramatiq.actor import Actor
from psqlextra.query import ConflictAction
from psqlextra.types import ConflictAction
if TYPE_CHECKING:
from authentik.tasks.schedules.models import Schedule
@@ -15,7 +16,7 @@ class ScheduleSpec:
actor: Actor
crontab: str
paused: bool = False
identifier: str | None = None
identifier: str | UUID | None = None
uid: str | None = None
args: Iterable[Any] = field(default_factory=tuple)
@@ -41,6 +42,8 @@ class ScheduleSpec:
return pickle.dumps(options)
def update_or_create(self) -> "Schedule":
from django.contrib.contenttypes.models import ContentType
from authentik.tasks.schedules.models import Schedule
update_values = {
@@ -50,10 +53,12 @@ class ScheduleSpec:
"kwargs": self.get_kwargs(),
"options": self.get_options(),
}
if self.rel_obj is not None:
update_values["rel_obj_content_type"] = ContentType.objects.get_for_model(self.rel_obj)
update_values["rel_obj_id"] = str(self.rel_obj.pk)
create_values = {
**update_values,
"crontab": self.crontab,
"rel_obj": self.rel_obj,
}
schedule = Schedule.objects.on_conflict(
@@ -62,7 +67,7 @@ class ScheduleSpec:
update_values=update_values,
).insert_and_get(
actor_name=self.actor.actor_name,
identifier=self.identifier,
identifier=str(self.identifier),
**create_values,
)

View File

@@ -13,6 +13,7 @@ def post_save_scheduled_model(sender, instance, **_):
return
for spec in instance.schedule_specs:
spec.rel_obj = instance
spec.identifier = instance.pk
schedule = spec.update_or_create()
if spec.send_on_save:
schedule.send()

View File

@@ -5,10 +5,3 @@ setup()
import django # noqa: E402
django.setup()
from authentik.root.signals import post_startup, pre_startup, startup # noqa: E402
_startup_sender = type("WorkerStartup", (object,), {})
pre_startup.send(sender=_startup_sender)
startup.send(sender=_startup_sender)
post_startup.send(sender=_startup_sender)

View File

@@ -1,6 +1,7 @@
from random import choice
from django.conf import settings
from django.db import DEFAULT_DB_ALIAS, connections
class FailoverRouter:
@@ -10,16 +11,22 @@ class FailoverRouter:
def __init__(self) -> None:
super().__init__()
self.database_aliases = set(settings.DATABASES.keys())
self.read_replica_aliases = list(self.database_aliases - {"default"})
self.read_replica_aliases = list(self.database_aliases - {DEFAULT_DB_ALIAS})
self.replica_enabled = len(self.read_replica_aliases) > 0
def db_for_read(self, model, **hints):
if not self.replica_enabled:
return "default"
return DEFAULT_DB_ALIAS
# Stay on primary for the entire transaction to maintain consistency.
# Reading from a replica mid-transaction would give a different snapshot,
# breaking transactional semantics (not just read-your-writes, but the
# entire consistent point-in-time view that a transaction provides).
if connections[DEFAULT_DB_ALIAS].in_atomic_block:
return DEFAULT_DB_ALIAS
return choice(self.read_replica_aliases) # nosec
def db_for_write(self, model, **hints):
return "default"
return DEFAULT_DB_ALIAS
def allow_relation(self, obj1, obj2, **hints):
"""Relations between objects are allowed if both objects are

View File

@@ -5,7 +5,8 @@ from json import loads
from django.urls import reverse
from django_tenants.utils import get_public_schema_name
from authentik.core.models import Token, TokenIntents, User
from authentik.core.models import Token, TokenIntents
from authentik.core.tests.utils import create_test_user
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.tenants.models import Tenant
@@ -21,7 +22,7 @@ class TestRecovery(TenantAPITestCase):
def setUp(self):
super().setUp()
self.tenant = Tenant.objects.get(schema_name=get_public_schema_name())
self.user: User = User.objects.create_user(username="recovery-test-user")
self.user = create_test_user()
@CONFIG.patch("outposts.disable_embedded_outpost", True)
@CONFIG.patch("tenants.enabled", True)

View File

@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2025.10.0-rc1 Blueprint schema",
"title": "authentik 2025.10.4 Blueprint schema",
"required": [
"version",
"entries"

View File

@@ -60,22 +60,6 @@ func checkServer() int {
return 0
}
func splitHostPort(address string) (host, port string) {
lastColon := strings.LastIndex(address, ":")
if lastColon == -1 {
return address, ""
}
host = address[:lastColon]
port = address[lastColon+1:]
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return host, port
}
func checkWorker() int {
pidB, err := os.ReadFile(workerPidFile)
if err != nil {
@@ -98,41 +82,6 @@ func checkWorker() int {
log.WithError(err).Warning("failed to signal worker process")
return 1
}
h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport),
}
host, port := splitHostPort(config.Get().Listen.HTTP)
if host == "0.0.0.0" || host == "::" {
url := fmt.Sprintf("http://%s:%s/-/health/ready/", "::1", port)
_, err := h.Head(url)
if err != nil {
log.WithError(err).WithField("url", url).Warning("failed to send healthcheck request")
url := fmt.Sprintf("http://%s:%s/-/health/ready/", "127.0.0.1", port)
res, err := h.Head(url)
if err != nil {
log.WithError(err).WithField("url", url).Warning("failed to send healthcheck request")
return 1
}
if res.StatusCode >= 400 {
log.WithField("status", res.StatusCode).Warning("unhealthy status code")
return 1
}
}
} else {
url := fmt.Sprintf("http://%s:%s/-/health/ready/", host, port)
res, err := h.Head(url)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")
return 1
}
if res.StatusCode >= 400 {
log.WithField("status", res.StatusCode).Warning("unhealthy status code")
return 1
}
}
log.Info("successfully checked health")
return 0
}

View File

@@ -31,7 +31,7 @@ services:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.0-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.4}
ports:
- ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
@@ -52,7 +52,7 @@ services:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.0-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.4}
restart: unless-stopped
user: root
volumes:

4
go.mod
View File

@@ -1,8 +1,6 @@
module goauthentik.io
go 1.24.3
toolchain go1.24.6
go 1.25.3
require (
beryju.io/ldap v0.1.0

View File

@@ -1 +1 @@
2025.10.0-rc1
2025.10.4

View File

@@ -6,7 +6,7 @@ import (
)
func OpensslVersion() string {
cmd := exec.Command("openssl", "version")
cmd := exec.Command("/usr/bin/openssl", "version")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()

View File

@@ -83,7 +83,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
// The service account this token belongs to should only have access to a single outpost
outposts, _ := retry.DoWithData[*api.PaginatedOutpostList](
func() (*api.PaginatedOutpostList, error) {
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
outposts, _, err := apiClient.OutpostsAPI.OutpostsInstancesList(context.Background()).Execute()
return outposts, err
},
retry.Attempts(0),
@@ -93,13 +93,13 @@ func NewAPIController(akURL url.URL, token string) *APIController {
}),
)
if len(outposts.Results) < 1 {
log.Panic("No outposts found with given token, ensure the given token corresponds to an authenitk Outpost")
log.Panic("No outposts found with given token, ensure the given token corresponds to an authentik Outpost")
}
outpost := outposts.Results[0]
log.WithField("name", outpost.Name).Debug("Fetched outpost configuration")
akConfig, _, err := apiClient.RootApi.RootConfigRetrieve(context.Background()).Execute()
akConfig, _, err := apiClient.RootAPI.RootConfigRetrieve(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch global configuration")
return nil
@@ -122,6 +122,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
eventHandlers: []EventHandler{},
refreshHandlers: make([]func(), 0),
}
ac.logger.WithField("embedded", ac.IsEmbedded()).Info("Outpost mode")
ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
err = ac.initEvent(akURL, outpost.Pk)
if err != nil {
@@ -135,6 +136,13 @@ func (a *APIController) Log() *log.Entry {
return a.logger
}
func (a *APIController) IsEmbedded() bool {
if m := a.Outpost.Managed.Get(); m != nil {
return *m == "goauthentik.io/outposts/embedded"
}
return false
}
// Start Starts all handlers, non-blocking
func (a *APIController) Start() error {
err := a.Server.Refresh()
@@ -180,7 +188,7 @@ func (a *APIController) Token() string {
func (a *APIController) OnRefresh() error {
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
outposts, _, err := a.Client.OutpostsAPI.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration")
return err

View File

@@ -11,7 +11,7 @@ import (
)
type CryptoStore struct {
api *api.CryptoApiService
api *api.CryptoAPIService
log *log.Entry
@@ -19,7 +19,7 @@ type CryptoStore struct {
certificates map[string]*tls.Certificate
}
func NewCryptoStore(cryptoApi *api.CryptoApiService) *CryptoStore {
func NewCryptoStore(cryptoApi *api.CryptoAPIService) *CryptoStore {
return &CryptoStore{
api: cryptoApi,
log: log.WithField("logger", "authentik.outpost.cryptostore"),

View File

@@ -148,7 +148,7 @@ func (fe *FlowExecutor) SetSession(s *http.Cookie) {
func (fe *FlowExecutor) WarmUp() error {
gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")
defer gcsp.Finish()
req := fe.api.FlowsApi.FlowsExecutorGet(gcsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
req := fe.api.FlowsAPI.FlowsExecutorGet(gcsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
_, _, err := req.Execute()
return err
}
@@ -165,7 +165,7 @@ func (fe *FlowExecutor) Execute() (bool, error) {
func (fe *FlowExecutor) getInitialChallenge() (*api.ChallengeTypes, error) {
// Get challenge
gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")
req := fe.api.FlowsApi.FlowsExecutorGet(gcsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
req := fe.api.FlowsAPI.FlowsExecutorGet(gcsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
challenge, _, err := req.Execute()
if err != nil {
return nil, err
@@ -188,7 +188,7 @@ func (fe *FlowExecutor) getInitialChallenge() (*api.ChallengeTypes, error) {
func (fe *FlowExecutor) solveFlowChallenge(challenge *api.ChallengeTypes, depth int) (bool, error) {
// Resole challenge
scsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.solve_challenge")
responseReq := fe.api.FlowsApi.FlowsExecutorSolve(scsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
responseReq := fe.api.FlowsAPI.FlowsExecutorSolve(scsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
i := challenge.GetActualInstance()
if i == nil {
return false, errors.New("response request instance was null")

View File

@@ -59,7 +59,7 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
return ldap.LDAPResultInvalidCredentials, nil
}
access, _, err := fe.ApiClient().OutpostsApi.OutpostsLdapAccessCheck(
access, _, err := fe.ApiClient().OutpostsAPI.OutpostsLdapAccessCheck(
req.Context(), db.si.GetProviderID(),
).AppSlug(db.si.GetAppSlug()).Execute()
if !access.Access.Passing {
@@ -85,7 +85,7 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
req.Log().Info("User has access")
uisp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
userInfo, _, err := fe.ApiClient().CoreAPI.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),

View File

@@ -32,7 +32,7 @@ func NewServer(ac *ak.APIController) ak.Outpost {
ls := &LDAPServer{
log: log.WithField("logger", "authentik.outpost.ldap"),
ac: ac,
cs: ak.NewCryptoStore(ac.Client.CryptoApi),
cs: ak.NewCryptoStore(ac.Client.CryptoAPI),
providers: []*ProviderInstance{},
connections: map[string]net.Conn{},
connectionsSync: sync.Mutex{},

View File

@@ -31,7 +31,7 @@ func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
}
func (ls *LDAPServer) Refresh() error {
apiProviders, err := ak.Paginator(ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()), ak.PaginatorOptions{
apiProviders, err := ak.Paginator(ls.ac.Client.OutpostsAPI.OutpostsLdapList(context.Background()), ak.PaginatorOptions{
PageSize: 100,
Logger: ls.log,
})

Some files were not shown because too many files have changed in this diff Show More