Compare commits

..

69 Commits

Author SHA1 Message Date
authentik-automation[bot]
bce6ba2afa release: 2025.8.6 2026-02-12 15:27:43 +00:00
Marc 'risson' Schmitt
1b490c1b5a website/docs: fix lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-12 15:41:11 +01:00
Marc 'risson' Schmitt
9a038aa0fe lib: fix lint
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-02-12 15:41:07 +01:00
authentik-automation[bot]
c80112e5f8 security: CVE-2026-25227 (#20233)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:24:09 +01:00
authentik-automation[bot]
65ae736fe2 security: CVE-2026-25748 (#20234)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:23:47 +01:00
authentik-automation[bot]
271e7eae1c security: CVE-2026-25922 (#20235)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2026-02-12 15:20:41 +01:00
authentik-automation[bot]
8bf66b8987 website/docs: generate CVE sidebar (cherry-pick #20098 to version-2025.8) (#20099)
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:40 +01:00
Marc 'risson' Schmitt
6f9369283c root: update client-go generation (cherry-pick #19762 and #19906 to version-2025.8) (#19934)
* root: update client-go generation (cherry-pick #19762 to version-2025.12) (#19791)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* ci: always generate API clients (#19906)

* ci: always generate API clients

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

* fix missing respective actions

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

* fix flaky test

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

* mount generated client

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

* sigh

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

---------

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

* ci: fix test_docker.sh

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* bump go

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

* fix dind

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-02-07 23:00:10 +01:00
authentik-automation[bot]
859ef3a722 website/docs: Remove stale 2024 version directives (cherry-pick #19888 to version-2025.8) (#20021)
* Cherry-pick #19888 to version-2025.8 (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>

* sigh

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

* not fail

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:20:48 +01:00
authentik-automation[bot]
b47ed8894c release: 2025.8.5 2025-11-19 15:05:03 +00:00
authentik-automation[bot]
8c1f08fecb website/docs: add 2025.8.5 and 2025.10.2 release notes (cherry-pick #18268 to version-2025.8) (#18269)
Cherry-pick #18268 to version-2025.8 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #18268
Original commit: cd1693be28

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

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:21:03 +01:00
authentik-automation[bot]
76815cd5b9 internal: Automated internal backport: 1487-invitation-expiry.sec.patch to authentik-2025.8 (#18261)
* Automated internal backport of patch 1487-invitation-expiry.sec.patch to authentik-2025.8

* lint

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-11-19 15:10:10 +01:00
authentik-automation[bot]
57a378bbd0 internal: Automated internal backport: 1498-oauth2-cc-user-active.sec.patch to authentik-2025.8 (#18262)
Automated internal backport of patch 1498-oauth2-cc-user-active.sec.patch to authentik-2025.8

Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-11-19 15:01:25 +01:00
Marcelo Elizeche Landó
f542a0d415 core: bump Django from 5.1.13 to 5.1.14 for 2025.8 (#17968)
* upgrade django from 5.1.13 to 5.1.14

* longer urls

* sync package-lock.json

* fix go deprecation for +build

* update package.lock
2025-11-11 12:20:45 +01:00
Jens L.
f69bbbe85e ci: fix migrate-from-stable for old versions (#18018)
* ci: fix migrate-from-stable for old versions

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

* version from authentik

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

* npm i

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

* npm i on linux

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

* fix outpost lint

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

* gha notice

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

* idk man I hate node

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-07 17:45:12 +01:00
Jens L.
0845e23337 ci: rework internal repo (#17797) (#17830)
* ci: rework internal repo (#17797)

* ci: rework internal repo

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

* also fix retention workflow

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	.github/workflows/gh-ghcr-retention.yml
#	.github/workflows/repo-mirror-cleanup.yml
#	.github/workflows/repo-mirror.yml

* fix package

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-01 20:08:15 +01:00
authentik-automation[bot]
de6d2a02b2 website/docs: update SAML provider docs (cherry-pick #15887 to version-2025.8) (#17583)
* Cherry-pick #15887 to version-2025.8 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #15887
Original commit: 83603a528f

* Fix sidebar conflict

* Remove SAML logout link

---------

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
2025-10-22 18:37:48 +02:00
authentik-automation[bot]
73be5321a9 website: add powershell syntax highlighting and bump package (cherry-pick #16683) (#16721)
website: add powershell syntax highlighting and bump package (#16683)

Add powershell syntax highlighting and bump package

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-10-10 13:43:24 +02:00
authentik-automation[bot]
ff8ad31a6b website/docs: add email config section (cherry-pick #16727 to version-2025.8) (#17364)
website/docs: add email config section (#16727)

* Add email section and link to it from install guide



* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Typo

* WIP

* Apply suggestion

* Added TLS email config

* Apply suggestions

* Apply suggestions

* fix linting

* fix broken anchor

* Apply suggestions

* Fix extra line

---------

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-10-09 22:19:40 +00:00
authentik-automation[bot]
ad2cedac98 website/docs: add entra id scim source (cherry-pick #17357 to version-2025.8) (#17362)
* Cherry-pick #17357 to version-2025.8 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #17357
Original commit: 2e03270dcf

* Conflict fix

---------

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-10-09 18:40:47 +00:00
authentik-automation[bot]
7a0bd0d0c1 web/admin: fix incorrect placeholder for scim provider (cherry-pick #17308 to version-2025.8) (#17309)
* Cherry-pick #17308 to version-2025.8 (with conflicts)

This cherry-pick has conflicts that need manual resolution.

Original PR: #17308
Original commit: 88583ae46b

* Update LDAPProviderFormForm.ts

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

* Update LDAPProviderFormForm.ts

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-08 16:54:18 +02:00
authentik-automation[bot]
269551cf4d blueprints: ensure tasks retry on database errors (cherry-pick #17333 to version-2025.8) (#17334)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-08 16:48:04 +02:00
authentik-automation[bot]
23a6ab915b lib/sync/outgoing: revert reduce number of db queries made (revert #14177) (cherry-pick #17306 to version-2025.8) (#17330)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-08 11:30:08 +00:00
authentik-automation[bot]
0c77e5c33e web: Fix behavior for modals configured with closeAfterSuccessfulSubmit (cherry-pick #17277 to version-2025.8) (#17299)
web: Fix behavior for modals configured with closeAfterSuccessfulSubmit (#17277)

when a form inside a modal submits successfully, it dispatches an EVENT_REFRESH event that bubbles up through the DOM. Parent components like TablePage listen for this event to refresh their data.
so, when the parent component refreshes/re-renders in response to EVENT_REFRESH, it destroys and recreates the entire row including the modal element and that causes the modal to disappear even
though the ModalForm component never explicitly closed it.

Co-authored-by: Dominic R <dominic@sdko.org>
2025-10-07 13:58:44 -04:00
authentik-automation[bot]
93aeae5405 core: fix absolute and relative path file uploads (cherry-pick #17269 to version-2025.8) (#17272)
core: fix absolute and relative path file uploads (#17269)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-06 19:05:09 +02:00
authentik-automation[bot]
cd3ff47221 tasks/middlewares/messages: make sure exceptions are always logged (cherry-pick #17237 to version-2025.8) (#17248)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-04 16:42:26 +02:00
authentik-automation[bot]
a370a54db8 packages/django-dramatiq-postgres: fix error when updating task with no changes (cherry-pick #16728 to version-2025.8) (#17238)
packages/django-dramatiq-postgres: fix error when updating task with no changes (#16728)

Co-authored-by: Jens L. <jens@goauthentik.io>
2025-10-03 17:12:02 +02:00
authentik-automation[bot]
1d0e45abe8 packages/django-dramatiq-postgres: broker: fix task expiration (cherry-pick #17178 to version-2025.8) (#17217)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-10-02 14:49:57 +02:00
authentik-automation[bot]
a501b627eb build(deps): bump django from 5.1.12 to 5.1.13 (cherry-pick #17198 to version-2025.8) (#17199)
build(deps): bump django from 5.1.12 to 5.1.13 (#17198)

* build(deps): bump django from 5.1.12 to 5.1.13

Bumps [django](https://github.com/django/django) from 5.1.12 to 5.1.13.
- [Commits](https://github.com/django/django/compare/5.1.12...5.1.13)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.1.13
  dependency-type: direct:production
...



* lock



---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-10-02 11:57:19 +02:00
authentik-automation[bot]
78a4c08fc8 website/docs: developer docs: adjust sentence for writing docs (cherry-pick #17137 to version-2025.8) (#17142)
website/docs: developer docs: adjust sentence for writing docs (#17137)

As per Tana's request

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-09-30 15:52:22 +00:00
authentik-automation[bot]
8d3a289d12 release: 2025.8.4 2025-09-30 00:02:15 +00:00
Jens Langhammer
99e92bf998 root: fix rustup error during build when buildcache version is outdated
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-30 01:32:01 +02:00
authentik-automation[bot]
1df7b22d29 release: 2025.8.4 2025-09-29 21:52:52 +00:00
authentik-automation[bot]
af484738f6 website/docs: 2025.8.4 release notes (cherry-pick #17119 to version-2025.8) (#17120) 2025-09-29 23:44:03 +02:00
authentik-automation[bot]
19ba3c8453 tasks: reduce default number of retries and max backoff (cherry-pick #17107 to version-2025.8) (#17109)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-29 17:33:19 +00:00
authentik-automation[bot]
9d1b384baa packages/django-dramatiq-postgres: broker: fix new messages not being picked up when too many messages are waiting (cherry-pick #17106 to version-2025.8) (#17108)
packages/django-dramatiq-postgres: broker: fix new messages not being picked up when too many messages are waiting (#17106)

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-29 17:11:11 +00:00
authentik-automation[bot]
8a702b148f providers/oauth2: fix authentication error with identical app passwords (cherry-pick #17100 to version-2025.8) (#17103)
providers/oauth2: fix authentication error with identical app passwords (#17100)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-29 17:29:14 +02:00
authentik-automation[bot]
2db42a3a04 stages/identification: fix mismatched error messages (cherry-pick #17090 to version-2025.8) (#17104)
stages/identification: fix mismatched error messages (#17090)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-29 17:29:03 +02:00
authentik-automation[bot]
4403baaa28 outposts/ldap: add pwdChangeTime attribute (cherry-pick #17010 to version-2025.8) (#17101)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix ldap tests following #17010 (#17021)
2025-09-29 15:44:35 +02:00
authentik-automation[bot]
8030d4d734 cmd/server/healthcheck: info log success instead of debug (cherry-pick #17093 to version-2025.8) (#17097)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-29 15:26:48 +02:00
authentik-automation[bot]
4b7a78453c web: Fix layout class for row in LibraryPage (cherry-pick #16752 to version-2025.8) (#17091)
web: Fix layout class for 'row' in LibraryPage (#16752)

Fix layout class for 'row' in LibraryPage

Signed-off-by: Jérôme W. <jerome@wnetworks.org>
Co-authored-by: Jérôme W. <jerome@wnetworks.org>
2025-09-29 14:56:15 +02:00
authentik-automation[bot]
b9746106a0 rbac: optimize rbac assigned by users query (cherry-pick #17015 to version-2025.8) (#17092)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-29 12:47:45 +00:00
authentik-automation[bot]
faa47670ef web/admin: fix federation sources automatically selected (cherry-pick #17069 to version-2025.8) (#17070)
web/admin: fix federation sources automatically selected (#17069)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-28 20:30:19 +02:00
authentik-automation[bot]
add5f4e0cb tasks: fix logger name (cherry-pick #17009 to version-2025.8) (#17060)
* tasks: fix logger name (#17009)

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

* fix tests

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-09-27 02:33:23 +02:00
authentik-automation[bot]
7636c038d9 */bindings: order by pk (cherry-pick #17027) (#17053)
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-26 18:37:29 +02:00
authentik-automation[bot]
557f781f5c lib/config: fix listen settings (cherry-pick #17005) (#17023)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix listen settings (#17005)
2025-09-26 16:59:23 +02:00
authentik-automation[bot]
1997011328 website/docs: Update Github expression to handle non-OAuth sources gracefully (cherry-pick #17014) (#17029)
website/docs: Update Github expression to handle non-OAuth sources gracefully (#17014)

Co-authored-by: Patrick <tradiuz@gmail.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-26 14:24:10 +02:00
authentik-automation[bot]
dfdd5ebc8c lib: match exception_to_dict locals behaviour (cherry-pick #17006) (#17016)
lib: match exception_to_dict locals behaviour (#17006)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-25 17:53:36 +02:00
authentik-automation[bot]
42977caa6a core: add index on Group.is_superuser (cherry-pick #17011) (#17017)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-09-25 15:29:15 +00:00
authentik-automation[bot]
bce46c880d rbac: fix typo (cherry-pick #16476) (#17018)
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
fix typo (#16476)
2025-09-25 15:12:03 +00:00
authentik-automation[bot]
9c987c5694 website/docs: improve discord policies when also bound to non-oauth sources (cherry-pick #17008) (#17012)
website/docs: improve discord policies when also bound to non-oauth sources (#17008)

Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-25 15:53:40 +02:00
authentik-automation[bot]
f33e50c28c webiste/docs: add missing oauth endpoints (cherry-pick #16995) (#16999)
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-09-25 15:00:21 +02:00
authentik-automation[bot]
280d6febf7 website/docs: oauth provider: Add device and introspect to reserved slugs (cherry-pick #16994) (#16998)
Co-authored-by: Dominic R <dominic@sdko.org>
2025-09-25 14:59:56 +02:00
authentik-automation[bot]
3a32918ceb providers/ldap: add include_children parameter to cached search mode (cherry-pick #16918) (#17000)
Co-authored-by: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
2025-09-25 12:58:24 +00:00
authentik-automation[bot]
723bd4bbbc website/developer docs: What domain for what doc version (cherry-pick #16987) (#16996)
website/developer docs: What domain for what doc version (#16987)

* website/developer docs: What domain for what doc version

Closes: AUTH-1316

* Apply suggestions from code review




---------

Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-09-25 08:35:30 -04:00
authentik-automation[bot]
7b130e1832 website/docs: update url to docker-compose.yml (cherry-pick #16901) (#16986)
website/docs: update url to docker-compose.yml (#16901)

Update URL of docker-compose.yml from "https://goauthentik.io/docker-compose.yml" to "https://docs.goauthentik.io/docker-compose.yml"

Co-authored-by: Nuno Alves <nuno.alves02@gmail.com>
2025-09-24 17:41:40 -04:00
authentik-automation[bot]
3aa969d64e website/docs: add docs for source switch expression policy (cherry-pick #16878) (#16972)
website/docs: add docs for source switch expression policy (#16878)

* draft for source switch docs

* add python

* add sidebar entry

* Update website/docs/customize/policies/expression/source_switch.md




* Update website/docs/customize/policies/index.md




* Update website/docs/customize/policies/working_with_policies.md




* Update website/docs/customize/policies/working_with_policies.md




* add sectin about binding the policy

* tweak

* Update website/docs/customize/policies/index.md




* Update website/docs/customize/policies/working_with_policies.md




* Update website/docs/customize/policies/working_with_policies.md




* Update website/docs/customize/policies/working_with_policies.md




* Update website/docs/customize/policies/working_with_policies.md




* Update website/docs/customize/policies/expression.mdx




---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Tana M Berry <tana@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-24 13:24:33 +01:00
authentik-automation[bot]
04688c86b3 website/docs: random typo fixes (cherry-pick #16956) (#16959)
website/docs: random typo fixes (#16956)

Co-authored-by: Jared Harrison <50091069+Jared-Is-Coding@users.noreply.github.com>
2025-09-23 23:57:14 +02:00
authentik-automation[bot]
4f8dbb5efb website/docs: fix capitalization (cherry-pick #16944) (#16958)
website/docs: fix capitalization (#16944)

* fix capitalization

* tweak

* Update website/docs/add-secure-apps/outposts/manual-deploy-docker-compose.md




---------

Signed-off-by: Tana M Berry <tanamarieberry@yahoo.com>
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-09-23 21:15:57 +01:00
authentik-automation[bot]
3b382199eb website/docs: 2025.8: fix worker concurrency setting rename (cherry-pick #16946) (#16950)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
fix worker concurrency setting rename (#16946)
2025-09-23 19:52:20 +02:00
authentik-automation[bot]
db50991e9c providers/scim: fix string formatting for SCIM user filter (cherry-pick #16465) (#16852)
providers/scim: fix string formatting for SCIM user filter (#16465)

* providers/scim: fix string formatting for SCIM user filter



* format

---------

Signed-off-by: Adam Shirt <adamshirt@outlook.com>
Co-authored-by: Adam Shirt <adamshirt@outlook.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2025-09-17 13:48:18 +02:00
authentik-automation[bot]
88036ebe14 webiste/docs: improve user ref doc (cherry-pick #16779) (#16839)
webiste/docs: improve user ref doc (#16779)

* WIP

* WIP

* Update website/docs/users-sources/user/user_ref.mdx




* Language change

* Update website/docs/users-sources/user/user_ref.mdx




---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2025-09-16 22:17:41 +00:00
authentik-automation[bot]
680feaefa1 release: 2025.8.3 2025-09-16 15:09:16 +00:00
authentik-automation[bot]
8676cd3a43 website/docs: 2025.8.3 release notes (cherry-pick #16809) (#16810)
website/docs: 2025.8.3 release notes (#16809)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-16 16:40:32 +02:00
authentik-automation[bot]
53e0f6b734 stages/email_authenticator: Fix email mfa loop (cherry-pick #16579) (#16807)
stages/email_authenticator: Fix email mfa loop (#16579)

* Return error message instead of infinite loop

* Remove unused code

* check for existing email device

* revert to initial behaviour

* Add test for failing when the user already has an email device

Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2025-09-16 16:20:34 +02:00
authentik-automation[bot]
eaee475662 website/docs: update create oauth provider page (cherry-pick #16617) (#16806)
website/docs: update create oauth provider page (#16617)

* Updated the page to be more consistent with upcoming changes to the saml page

* Add note

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-09-16 13:33:53 +00:00
authentik-automation[bot]
19b672b3bc lifecycle: fix permission error when running worker as root (cherry-pick #16735) (#16784)
lifecycle: fix permission error when running worker as root (#16735)

* lifecycle: fix permission error when running worker as root



* fix maybe?



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2025-09-16 14:49:31 +02:00
authentik-automation[bot]
bf0a31ce86 website/docs: fix docker tabs not rendering properly (cherry-pick #16799) (#16802)
website/docs: fix docker tabs not rendering properly (#16799)

docs: fix docker tabs not rendering properly

Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2025-09-16 11:00:27 +01:00
177 changed files with 2871 additions and 818 deletions

View File

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

View File

@@ -10,7 +10,6 @@ on:
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

@@ -59,7 +59,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@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

@@ -14,7 +14,6 @@ on:
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

@@ -87,6 +87,11 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- uses: actions/setup-node@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@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@v3
with:

View File

@@ -47,8 +47,14 @@ jobs:
test:
name: Pre-release test
runs-on: ubuntu-latest
needs:
- check-inputs
steps:
- uses: actions/checkout@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

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@v5
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@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@v5
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: BeryJu/repository-mirroring-action@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

@@ -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.24-bookworm AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm 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,6 +58,7 @@ 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}" \
@@ -119,7 +121,11 @@ RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/v
libltdl-dev && \
curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec"
ENV UV_NO_BINARY_PACKAGE="cryptography lxml python-kadmin-rs xmlsec" \
# https://github.com/rust-lang/rustup/issues/2949
# Fixes issues where the rust version in the build cache is older than latest
# and rustup tries to update it, which fails
RUSTUP_PERMIT_COPY_RENAME="true"
RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
--mount=type=bind,target=uv.lock,src=uv.lock \

View File

@@ -144,12 +144,7 @@ gen-clean-ts: ## Remove generated API client for TypeScript
rm -rf ${PWD}/web/node_modules/@goauthentik/api/
gen-clean-go: ## Remove generated API client for Go
mkdir -p ${PWD}/${GEN_API_GO}
ifneq ($(wildcard ${PWD}/${GEN_API_GO}/.*),)
make -C ${PWD}/${GEN_API_GO} clean
else
rm -rf ${PWD}/${GEN_API_GO}
endif
gen-clean-py: ## Remove generated API client for Python
rm -rf ${PWD}/${GEN_API_PY}/
@@ -187,13 +182,9 @@ gen-client-py: gen-clean-py ## Build and install the authentik API for Python
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.8.2"
VERSION = "2025.8.6"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@@ -111,7 +111,6 @@ class BlueprintEventHandler(FileSystemEventHandler):
@actor(
description=_("Find blueprints as `blueprints_find` does, but return a safe dict."),
throws=(DatabaseError, ProgrammingError, InternalError),
priority=PRIORITY_HIGH,
)
def blueprints_find_dict():
@@ -150,10 +149,7 @@ def blueprints_find() -> list[BlueprintFile]:
return blueprints
@actor(
description=_("Find blueprints and check if they need to be created in the database."),
throws=(DatabaseError, ProgrammingError, InternalError),
)
@actor(description=_("Find blueprints and check if they need to be created in the database."))
def blueprints_discovery(path: str | None = None):
self: Task = CurrentTask.get_task()
count = 0

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

@@ -0,0 +1,18 @@
# Generated by Django 5.1.12 on 2025-09-25 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0050_user_last_updated_and_more"),
("authentik_rbac", "0006_alter_role_options"),
]
operations = [
migrations.AddIndex(
model_name="group",
index=models.Index(fields=["is_superuser"], name="authentik_c_is_supe_1e5a97_idx"),
),
]

View File

@@ -29,6 +29,7 @@ from authentik.blueprints.models import ManagedModel
from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.generators import generate_id
from authentik.lib.merge import MERGE_LIST_UNIQUE
@@ -200,7 +201,10 @@ class Group(SerializerModel, AttributesMixin):
"parent",
),
)
indexes = [models.Index(fields=["name"])]
indexes = (
models.Index(fields=["name"]),
models.Index(fields=["is_superuser"]),
)
verbose_name = _("Group")
verbose_name_plural = _("Groups")
permissions = [
@@ -563,8 +567,10 @@ class Application(SerializerModel, PolicyBindingModel):
it is returned as-is"""
if not self.meta_icon:
return None
if "://" in self.meta_icon.name or self.meta_icon.name.startswith("/static"):
if self.meta_icon.name.startswith("http"):
return self.meta_icon.name
if self.meta_icon.name.startswith("/"):
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:
@@ -766,8 +772,10 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
starts with http it is returned as-is"""
if not self.icon:
return None
if "://" in self.icon.name or self.icon.name.startswith("/static"):
if self.icon.name.startswith("http"):
return self.icon.name
if self.icon.name.startswith("/"):
return CONFIG.get("web.path", "/")[:-1] + self.icon.name
return self.icon.url
def get_user_path(self) -> str:

View File

@@ -82,6 +82,51 @@ class TestApplicationsAPI(APITestCase):
self.assertEqual(self.allowed.get_meta_icon, app["meta_icon"])
self.assertEqual(self.allowed.meta_icon.read(), b"text")
def test_set_icon_relative(self):
"""Test set_icon (relative path)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "relative/path"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "/media/public/relative/path")
def test_set_icon_absolute(self):
"""Test set_icon (absolute path)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "/relative/path"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "/relative/path")
def test_set_icon_url(self):
"""Test set_icon (url)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "https://authentik.company/img.png"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "https://authentik.company/img.png")
def test_check_access(self):
"""Test check_access operation"""
self.client.force_login(self.user)
@@ -134,6 +179,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),
@@ -188,6 +235,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

@@ -25,7 +25,7 @@ class GoogleWorkspaceGroupClient(
"""Google client for groups"""
connection_type = GoogleWorkspaceProviderGroup
connection_attr = "googleworkspaceprovidergroup_set"
connection_type_query = "group"
can_discover = True
def __init__(self, provider: GoogleWorkspaceProvider) -> None:

View File

@@ -20,7 +20,7 @@ class GoogleWorkspaceUserClient(GoogleWorkspaceSyncClient[User, GoogleWorkspaceP
"""Sync authentik users into google workspace"""
connection_type = GoogleWorkspaceProviderUser
connection_attr = "googleworkspaceprovideruser_set"
connection_type_query = "user"
can_discover = True
def __init__(self, provider: GoogleWorkspaceProvider) -> None:

View File

@@ -139,11 +139,7 @@ class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
if type == User:
# Get queryset of all users with consistent ordering
# according to the provider's settings
base = (
User.objects.prefetch_related("googleworkspaceprovideruser_set")
.all()
.exclude_anonymous()
)
base = User.objects.all().exclude_anonymous()
if self.exclude_users_service_account:
base = base.exclude(type=UserTypes.SERVICE_ACCOUNT).exclude(
type=UserTypes.INTERNAL_SERVICE_ACCOUNT
@@ -153,11 +149,7 @@ class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
return base.order_by("pk")
if type == Group:
# Get queryset of all groups with consistent ordering
return (
Group.objects.prefetch_related("googleworkspaceprovidergroup_set")
.all()
.order_by("pk")
)
return Group.objects.all().order_by("pk")
raise ValueError(f"Invalid type {type}")
def google_credentials(self):

View File

@@ -29,7 +29,7 @@ class MicrosoftEntraGroupClient(
"""Microsoft client for groups"""
connection_type = MicrosoftEntraProviderGroup
connection_attr = "microsoftentraprovidergroup_set"
connection_type_query = "group"
can_discover = True
def __init__(self, provider: MicrosoftEntraProvider) -> None:

View File

@@ -24,7 +24,7 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
"""Sync authentik users into microsoft entra"""
connection_type = MicrosoftEntraProviderUser
connection_attr = "microsoftentraprovideruser_set"
connection_type_query = "user"
can_discover = True
def __init__(self, provider: MicrosoftEntraProvider) -> None:

View File

@@ -128,11 +128,7 @@ class MicrosoftEntraProvider(OutgoingSyncProvider, BackchannelProvider):
if type == User:
# Get queryset of all users with consistent ordering
# according to the provider's settings
base = (
User.objects.prefetch_related("microsoftentraprovideruser_set")
.all()
.exclude_anonymous()
)
base = User.objects.all().exclude_anonymous()
if self.exclude_users_service_account:
base = base.exclude(type=UserTypes.SERVICE_ACCOUNT).exclude(
type=UserTypes.INTERNAL_SERVICE_ACCOUNT
@@ -142,11 +138,7 @@ class MicrosoftEntraProvider(OutgoingSyncProvider, BackchannelProvider):
return base.order_by("pk")
if type == Group:
# Get queryset of all groups with consistent ordering
return (
Group.objects.prefetch_related("microsoftentraprovidergroup_set")
.all()
.order_by("pk")
)
return Group.objects.all().order_by("pk")
raise ValueError(f"Invalid type {type}")
def microsoft_credentials(self):

View File

@@ -46,5 +46,5 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet):
serializer_class = FlowStageBindingSerializer
filterset_fields = "__all__"
search_fields = ["stage__name"]
ordering = ["order"]
ordering_fields = ["order", "stage__name"]
ordering = ["order", "pk"]
ordering_fields = ["order", "stage__name", "target__uuid", "pk"]

View File

@@ -190,7 +190,7 @@ class Flow(SerializerModel, PolicyBindingModel):
)
if self.background.name.startswith("http"):
return self.background.name
if self.background.name.startswith("/static"):
if self.background.name.startswith("/"):
return CONFIG.get("web.path", "/")[:-1] + self.background.name
return self.background.url

View File

@@ -19,7 +19,7 @@ def start_debug_server(**kwargs) -> bool:
)
return False
listen: str = CONFIG.get("listen.listen_debug_py", "127.0.0.1:9901")
listen: str = CONFIG.get("listen.debug_py", "127.0.0.1:9901")
host, _, port = listen.rpartition(":")
try:
debugpy.listen((host, int(port)), **kwargs) # nosec

View File

@@ -31,14 +31,14 @@ postgresql:
# host: replica1.example.com
listen:
listen_http: 0.0.0.0:9000
listen_https: 0.0.0.0:9443
listen_ldap: 0.0.0.0:3389
listen_ldaps: 0.0.0.0:6636
listen_radius: 0.0.0.0:1812
listen_metrics: 0.0.0.0:9300
listen_debug: 0.0.0.0:9900
listen_debug_py: 0.0.0.0:9901
http: 0.0.0.0:9000
https: 0.0.0.0:9443
ldap: 0.0.0.0:3389
ldaps: 0.0.0.0:6636
radius: 0.0.0.0:1812
metrics: 0.0.0.0:9300
debug: 0.0.0.0:9900
debug_py: 0.0.0.0:9901
trusted_proxy_cidrs:
- 127.0.0.0/8
- 10.0.0.0/8
@@ -152,7 +152,7 @@ worker:
processes: 1
threads: 2
consumer_listen_timeout: "seconds=30"
task_max_retries: 20
task_max_retries: 5
task_default_time_limit: "minutes=10"
lock_purge_interval: "minutes=1"
task_purge_interval: "days=1"

View File

@@ -36,7 +36,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:
@@ -218,7 +218,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

@@ -22,6 +22,7 @@ if TYPE_CHECKING:
class Direction(StrEnum):
add = "add"
remove = "remove"
@@ -35,16 +36,13 @@ SAFE_METHODS = [
class BaseOutgoingSyncClient[
TModel: "Model",
TConnection: "Model",
TSchema: dict,
TProvider: "OutgoingSyncProvider",
TModel: "Model", TConnection: "Model", TSchema: dict, TProvider: "OutgoingSyncProvider"
]:
"""Basic Outgoing sync client Client"""
provider: TProvider
connection_type: type[TConnection]
connection_attr: str
connection_type_query: str
mapper: PropertyMappingManager
can_discover = False
@@ -64,7 +62,9 @@ class BaseOutgoingSyncClient[
def write(self, obj: TModel) -> tuple[TConnection, bool]:
"""Write object to destination. Uses self.create and self.update, but
can be overwritten for further logic"""
connection = getattr(obj, self.connection_attr).filter(provider=self.provider).first()
connection = self.connection_type.objects.filter(
provider=self.provider, **{self.connection_type_query: obj}
).first()
try:
if not connection:
connection = self.create(obj)

View File

@@ -1,5 +1,7 @@
"""Test Evaluator base functions"""
from pathlib import Path
from django.test import RequestFactory, TestCase
from django.urls import reverse
from jwt import decode
@@ -77,3 +79,18 @@ class TestEvaluator(TestCase):
jwt, provider.client_secret, algorithms=["HS256"], audience=provider.client_id
)
self.assertEqual(decoded["preferred_username"], user.username)
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

@@ -4,9 +4,11 @@ from traceback import extract_tb
from structlog.tracebacks import ExceptionDictTransformer
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import class_to_path
TRACEBACK_HEADER = "Traceback (most recent call last):"
_exception_transformer = ExceptionDictTransformer(show_locals=CONFIG.get_bool("debug"))
def exception_to_string(exc: Exception) -> str:
@@ -23,4 +25,4 @@ def exception_to_string(exc: Exception) -> str:
def exception_to_dict(exc: Exception) -> dict:
"""Format exception as a dictionary"""
return ExceptionDictTransformer()((type(exc), exc, exc.__traceback__))
return _exception_transformer((type(exc), exc, exc.__traceback__))

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

@@ -13,6 +13,7 @@ from urllib3.exceptions import HTTPError
from yaml import dump_all
from authentik.events.logs import LogEvent, capture_logs
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
@@ -105,7 +106,7 @@ class KubernetesController(BaseController):
LogEvent(
log_level="info",
event=f"{reconcile_key.title()}: Disabled",
logger=str(type(self)),
logger=class_to_path(self.__class__),
)
)
continue
@@ -144,7 +145,7 @@ class KubernetesController(BaseController):
LogEvent(
log_level="info",
event=f"{reconcile_key.title()}: Disabled",
logger=str(type(self)),
logger=class_to_path(self.__class__),
)
)
continue

View File

@@ -124,4 +124,5 @@ class PolicyBindingViewSet(UsedByMixin, ModelViewSet):
serializer_class = PolicyBindingSerializer
search_fields = ["policy__name"]
filterset_class = PolicyBindingFilter
ordering = ["target", "order"]
ordering = ["order", "pk"]
ordering_fields = ["order", "target__uuid", "pk"]

View File

@@ -8,7 +8,12 @@ from jwt import decode
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, Token, TokenIntents, UserTypes
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import (
create_test_admin_user,
create_test_cert,
create_test_flow,
create_test_user,
)
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
GRANT_TYPE_CLIENT_CREDENTIALS,
@@ -121,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")
@@ -182,6 +211,47 @@ class TestTokenClientCredentialsUserNamePassword(OAuthTestCase):
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
def test_successful_two_tokens(self):
"""test successful when two app passwords with the same key exist"""
Token.objects.create(
identifier="sa-token-two",
user=create_test_user(),
intent=TokenIntents.INTENT_APP_PASSWORD,
expiring=False,
key=self.token.key,
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"username": "sa",
"password": self.token.key,
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
_, alg = self.provider.jwt_key
jwt = decode(
body["access_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
jwt = decode(
body["id_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], self.user.name)
self.assertEqual(jwt["preferred_username"], self.user.username)
def test_successful_password(self):
"""test successful (password grant)"""
response = self.client.post(

View File

@@ -336,11 +336,11 @@ 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(
key=password, intent=TokenIntents.INTENT_APP_PASSWORD
key=password, intent=TokenIntents.INTENT_APP_PASSWORD, user=user
).first()
if not token or token.user.uid != user.uid:
raise TokenError("invalid_grant")

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

@@ -38,7 +38,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
"""SCIM client for groups"""
connection_type = SCIMProviderGroup
connection_attr = "scimprovidergroup_set"
connection_type_query = "group"
mapper: PropertyMappingManager
def __init__(self, provider: SCIMProvider):

View File

@@ -18,7 +18,7 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
"""SCIM client for users"""
connection_type = SCIMProviderUser
connection_attr = "scimprovideruser_set"
connection_type_query = "user"
mapper: PropertyMappingManager
def __init__(self, provider: SCIMProvider):
@@ -72,7 +72,8 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
if not self._config.filter.supported:
raise exc
users = self._request(
"GET", f"/Users?{urlencode({'filter': f'userName eq {scim_user.userName}'})}"
"GET",
f"/Users?{urlencode({'filter': f'userName eq \"{scim_user.userName}\"'})}",
)
users_res = users.get("Resources", [])
if len(users_res) < 1:

View File

@@ -123,7 +123,7 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
if type == User:
# Get queryset of all users with consistent ordering
# according to the provider's settings
base = User.objects.prefetch_related("scimprovideruser_set").all().exclude_anonymous()
base = User.objects.all().exclude_anonymous()
if self.exclude_users_service_account:
base = base.exclude(type=UserTypes.SERVICE_ACCOUNT).exclude(
type=UserTypes.INTERNAL_SERVICE_ACCOUNT
@@ -133,7 +133,7 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
return base.order_by("pk")
if type == Group:
# Get queryset of all groups with consistent ordering
return Group.objects.prefetch_related("scimprovidergroup_set").all().order_by("pk")
return Group.objects.all().order_by("pk")
raise ValueError(f"Invalid type {type}")
@property

View File

@@ -1,5 +1,6 @@
"""common RBAC serializers"""
from django.contrib.auth.models import Permission
from django.db.models import Q, QuerySet
from django.db.transaction import atomic
from django_filters.filters import CharFilter, ChoiceFilter
@@ -17,7 +18,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import User, UserTypes
from authentik.core.models import Group, User, UserTypes
from authentik.policies.event_matcher.models import model_choices
from authentik.rbac.api.rbac import PermissionAssignResultSerializer, PermissionAssignSerializer
from authentik.rbac.decorators import permission_required
@@ -54,26 +55,56 @@ class UserAssignedPermissionFilter(FilterSet):
model = ChoiceFilter(choices=model_choices(), method="filter_model", required=True)
object_pk = CharFilter(method="filter_object_pk")
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
data = self.form.cleaned_data
model: str = data["model"]
object_pk: str | None = data.get("object_pk", None)
app, _, model = model.partition(".")
superuser_pks = (
Group.objects.filter(is_superuser=True).values_list("users", flat=True).distinct()
)
permissions = Permission.objects.filter(
content_type__app_label=app,
content_type__model=model,
)
user_pks_with_model_permission = (
permissions.order_by().values_list("user", flat=True).distinct()
)
user_pks_with_object_permission = []
if object_pk:
user_pks_with_object_permission = (
UserObjectPermission.objects.filter(
permission__in=permissions,
object_pk=object_pk,
)
.order_by()
.values_list("user", flat=True)
.distinct()
)
return queryset.filter(
Q(pk__in=superuser_pks)
| Q(pk__in=user_pks_with_model_permission)
| Q(pk__in=user_pks_with_object_permission)
)
def filter_model(self, queryset: QuerySet, name, value: str) -> QuerySet:
"""Filter by object type"""
app, _, model = value.partition(".")
return queryset.filter(
Q(
user_permissions__content_type__app_label=app,
user_permissions__content_type__model=model,
)
| Q(
userobjectpermission__permission__content_type__app_label=app,
userobjectpermission__permission__content_type__model=model,
)
| Q(ak_groups__is_superuser=True)
).distinct()
# Actual filtering is handled by the above method where both `model` and `object_pk` are
# available. Don't do anything here, this method is only left here to avoid overriding too
# much of filter_queryset.
return queryset
def filter_object_pk(self, queryset: QuerySet, name, value: str) -> QuerySet:
"""Filter by object primary key"""
return queryset.filter(
Q(userobjectpermission__object_pk=value) | Q(ak_groups__is_superuser=True),
).distinct()
# Actual filtering is handled by the above method where both `model` and `object_pk` are
# available. Don't do anything here, this method is only left here to avoid overriding too
# much of filter_queryset.
return queryset
class UserAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
@@ -83,7 +114,7 @@ class UserAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
ordering = ["username"]
# The filtering is done in the filterset,
# which has a required filter that does the heavy lifting
queryset = User.objects.all()
queryset = User.objects.all().prefetch_related("userobjectpermission_set")
filterset_class = UserAssignedPermissionFilter
@permission_required("authentik_core.assign_user_permissions")

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.11 on 2025-08-29 14:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_rbac", "0005_initialpermissions"),
]
operations = [
migrations.AlterModelOptions(
name="role",
options={
"permissions": [
("assign_role_permissions", "Can assign permissions to roles"),
("unassign_role_permissions", "Can unassign permissions from roles"),
],
"verbose_name": "Role",
"verbose_name_plural": "Roles",
},
),
]

View File

@@ -71,8 +71,8 @@ class Role(SerializerModel):
verbose_name = _("Role")
verbose_name_plural = _("Roles")
permissions = [
("assign_role_permissions", _("Can assign permissions to users")),
("unassign_role_permissions", _("Can unassign permissions from users")),
("assign_role_permissions", _("Can assign permissions to roles")),
("unassign_role_permissions", _("Can unassign permissions from roles")),
]

View File

@@ -6,6 +6,7 @@ from hashlib import sha512
from pathlib import Path
import orjson
from django.http import response as http_response
from sentry_sdk import set_tag
from xmlsec import enable_debug_trace
@@ -412,7 +413,10 @@ DRAMATIQ = {
("dramatiq.middleware.pipelines.Pipelines", {}),
(
"dramatiq.middleware.retries.Retries",
{"max_retries": CONFIG.get_int("worker.task_max_retries") if not TEST else 0},
{
"max_retries": CONFIG.get_int("worker.task_max_retries") if not TEST else 0,
"max_backoff": 60 * 60 * 1000, # 1 hour
},
),
("dramatiq.results.middleware.Results", {"store_results": True}),
("django_dramatiq_postgres.middleware.CurrentTask", {}),
@@ -457,6 +461,13 @@ 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..
http_response.MAX_URL_LENGTH = http_response.MAX_URL_LENGTH * 4
# Media files
if CONFIG.get("storage.media.backend", "file") == "s3":
STORAGES["default"] = {

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 (
@@ -214,9 +215,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"""
@@ -137,6 +140,10 @@ class ResponseProcessor:
except xmlsec.Error as exc:
raise InvalidSignature() from exc
LOGGER.debug("Successfully verified signature")
parent = signature_nodes[0].getparent()
if parent is None or parent.tag != f"{{{NS_SAML_ASSERTION}}}Assertion":
raise InvalidSignature("No Signature exists in the Assertion element.")
self._assertion = parent
def _verify_request_id(self):
if self._source.allow_idp_initiated:
@@ -201,14 +208,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")
@@ -261,6 +275,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,41 @@
<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="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

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

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFUzCCAzugAwIBAgIRAL6tbNcE9Ej9gNlbGKswfFMwDQYJKoZIhvcNAQELBQAw
HTEbMBkGA1UEAwwSYXV0aGVudGlrIDIwMjUuNi4zMB4XDTI1MDcxNTE4MDQzNloX
DTI2MDcxNjE4MDQzNlowVjEqMCgGA1UEAwwhYXV0aGVudGlrIFNlbGYtc2lnbmVk
IENlcnRpZmljYXRlMRIwEAYDVQQKDAlhdXRoZW50aWsxFDASBgNVBAsMC1NlbGYt
c2lnbmVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjmut/+bBRLly
rbf+WIfg8ZTw9t6VnsiU1n04nPTulpRAz4nBOoOHNRIruSpZyFeFa6x9jwn4Ma5E
FUH7HqnRvhoujm8U17OglXWZt0DLCZ6S5xPmdMogFXjJDmg9okIcI/cb9VbR6I8u
vm1oiaOWCr36RTiqZ6rmdjQcuUPLr1+V/LxWQI463S+5QA2HZxAGalp45MJAz2sa
9iczktKMgyYlfjj1cruFARxxeheu5qIK7aQWfyPj1QlMb9mi4VQaxUwGrAui4Tq6
14ivRJY2SkZb0Aq/LLSQoQWYHtYyQIasrOXJm0JuPDqhINPBDowyhu8DihC3uzOp
mTXLKc5UoIQk+Q1h5iH74A3/kxOJUw13FXzRiDxC/yGthPYLyFHsDiJolscMKSCq
lDvEMcpM4mxFeud9sKUb71SZr8sqmJl3qtvZmKpkR4y8pN2c00p10t0htqONmr5k
yPxmhz0HCrosiPYB4olNjaydKviNTtPJ7TtnPyeA3iXGzCP1e80XzUoJrDqON5/G
cpYgqsP/kGj8Qvqesa4Fez+1+5pAGHN2VzQbkHAgK3s4YRXrGLTs7wg27F9T0RE2
8Mm0RYBkYpdp4/5PuTTulthB9mkUBSJMgENmQAYkapvonFDsJkTi39qnsddbZusO
LT4z3hsA38eFEwRqnbNZVUGPIp/O1SsCAwEAAaNVMFMwUQYDVR0RAQH/BEcwRYJD
RUZ4QXVLRzV6SlVUTWpWNTJoMkRJMUQ5MXdLblZKaXFwNmpwRTRTTy5zZWxmLXNp
Z25lZC5nb2F1dGhlbnRpay5pbzANBgkqhkiG9w0BAQsFAAOCAgEAYLThxDVpA1OI
AVK/buueRJExIWr6y4s6NtpuR8UQEcfq5hfoc4zMFGHR5+u1WFIb5siK25xh/own
7bLdLic6AkjZSrx91+0v2Jn9gfUqbs5AJ040XzAAdx/Mb4s0+537yhB+/JXPylR1
QxhGbO7koXQ5JDhAXWKCw2O1C+80mN8dbhQvDkEtsXrHrtXclcqf2TT89XAzc5HA
C8NmP4SF+FafAREQB1KdaG4QAbc/gnjsX2YJD89SDL+3jMp6F7R1Ym+bWt5oWqx2
tkm6HGXd3fbpfQlnfrRN60tMjjLmw1cDMhOhpdragY5zokniEUL2pKVtrxFp7V1Z
poMI0Kt5MKkOXrezi542NWSgkGehlsDLD9wtuCNem2arR0mNnMLdYkMG7G0dpAq3
Tl32dgfMfyKnNyE2O/6/EeEuzUH2NfTU1p7AUQfLrf4rtNcJEs9OAPuC9vy7w9YE
pF997T+FhR2Ub1C423NQj4bwlS/9f7MIBkSi1EgnQuiSGB5epxAKI3oOVrmzOpTu
vr6wZXV9pM3zdfbcoGuFWP6Ix7W8G5vg+0WvoSjc2fwGXYlidEK3xlQSMAaQ4CMC
lpPsKLScRq1nrQGzPYoiL1DYubsOWx9ohll6+jNjKI6f79WwbHYrW4EeRIOz38+m
46EDjAWZBMgrE7J/3DhgeLEVJYBA5K0=
-----END CERTIFICATE-----

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

@@ -125,3 +125,50 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
with self.assertRaises(InvalidEncryption):
parser.parse()
def test_verification_assertion(self):
"""Test verifying signature inside 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.xml").encode()
).decode()
},
)
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")

View File

@@ -142,6 +142,11 @@ class AuthenticatorEmailStageView(ChallengeStageView):
user = self.get_pending_user()
stage: AuthenticatorEmailStage = self.executor.current_stage
# For the moment we only allow one email device per user
if EmailDevice.objects.filter(Q(user=user), stage=stage.pk).exists():
return self.executor.stage_invalid(
_("The user already has an email address registered for MFA.")
)
if SESSION_KEY_EMAIL_DEVICE not in self.request.session:
device = EmailDevice(user=user, confirmed=False, stage=stage, name="Email Device")
valid_secs: int = timedelta_from_string(stage.token_expiry).total_seconds()

View File

@@ -108,6 +108,17 @@ class TestAuthenticatorEmailStage(FlowTestCase):
)
def test_stage_submit(self):
"""Test stage email submission"""
# test fail because of existing device
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-access-denied",
)
self.device.delete()
# Initialize the flow
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
@@ -232,6 +243,7 @@ class TestAuthenticatorEmailStage(FlowTestCase):
def test_challenge_generation(self):
"""Test challenge generation"""
# Test with masked email
self.device.delete()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)

View File

@@ -148,7 +148,10 @@ class IdentificationChallengeResponse(ChallengeResponse):
captcha_token = attrs.get("captcha_token", None)
if not captcha_token:
self.stage.logger.warning("Token not set for captcha attempt")
verify_captcha_token(captcha_stage, captcha_token, client_ip)
try:
verify_captcha_token(captcha_stage, captcha_token, client_ip)
except ValidationError:
raise ValidationError(_("Failed to authenticate.")) from None
# Password check
if not current_stage.password_stage:

View File

@@ -194,7 +194,7 @@ class TestIdentificationStage(FlowTestCase):
password_fields=False,
primary_action="Log in",
response_errors={
"non_field_errors": [{"code": "invalid", "string": "Invalid captcha response"}]
"non_field_errors": [{"code": "invalid", "string": "Failed to authenticate."}]
},
sources=[
{
@@ -247,7 +247,7 @@ class TestIdentificationStage(FlowTestCase):
"non_field_errors": [
{
"code": "invalid",
"string": "Invalid captcha response. Retrying may solve this issue.",
"string": "Failed to authenticate.",
}
]
},

View File

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

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,6 +18,7 @@ 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,
INVITATION_TOKEN_KEY,
INVITATION_TOKEN_KEY_CONTEXT,
PLAN_CONTEXT_PROMPT,
@@ -77,6 +80,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({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()])
@@ -94,6 +122,7 @@ class TestInvitationStage(FlowTestCase):
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(plan.context[INVITATION], invite)
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
self.assertEqual(response.status_code, 200)

View File

@@ -11,7 +11,7 @@ def worker_healthcheck():
import authentik.tasks.setup # noqa
from authentik.tasks.middleware import WorkerHealthcheckMiddleware
host, _, port = CONFIG.get("listen.listen_http").rpartition(":")
host, _, port = CONFIG.get("listen.http").rpartition(":")
try:
port = int(port)
@@ -33,7 +33,7 @@ def worker_metrics():
import authentik.tasks.setup # noqa
from authentik.tasks.middleware import MetricsMiddleware
addr, _, port = CONFIG.get("listen.listen_metrics").rpartition(":")
addr, _, port = CONFIG.get("listen.metrics").rpartition(":")
try:
port = int(port)

View File

@@ -21,6 +21,7 @@ from structlog.stdlib import get_logger
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.tasks.models import Task, TaskStatus, WorkerStatus
from authentik.tenants.models import Tenant
from authentik.tenants.utils import get_current_tenant
@@ -55,13 +56,13 @@ class RelObjMiddleware(Middleware):
class MessagesMiddleware(Middleware):
def after_enqueue(self, broker: Broker, message: Message, delay: int):
def after_enqueue(self, broker: Broker, message: Message, delay: int | None):
task: Task = message.options["task"]
task_created: bool = message.options["task_created"]
if task_created:
task._messages.append(
Task._make_message(
str(type(self)),
class_to_path(type(self)),
TaskStatus.INFO,
"Task has been queued",
delay=delay,
@@ -71,7 +72,7 @@ class MessagesMiddleware(Middleware):
task._previous_messages.extend(task._messages)
task._messages = [
Task._make_message(
str(type(self)),
class_to_path(type(self)),
TaskStatus.INFO,
"Task will be retried",
delay=delay,
@@ -81,7 +82,7 @@ class MessagesMiddleware(Middleware):
def before_process_message(self, broker: Broker, message: Message):
task: Task = message.options["task"]
task.log(str(type(self)), TaskStatus.INFO, "Task is being processed")
task.log(class_to_path(type(self)), TaskStatus.INFO, "Task is being processed")
def after_process_message(
self,
@@ -93,15 +94,19 @@ class MessagesMiddleware(Middleware):
):
task: Task = message.options["task"]
if exception is None:
task.log(str(type(self)), TaskStatus.INFO, "Task finished processing without errors")
return
if should_ignore_exception(exception):
task.log(
class_to_path(type(self)),
TaskStatus.INFO,
"Task finished processing without errors",
)
return
task.log(
str(type(self)),
class_to_path(type(self)),
TaskStatus.ERROR,
exception,
)
if should_ignore_exception(exception):
return
event_kwargs = {
"actor": task.actor_name,
}
@@ -115,7 +120,7 @@ class MessagesMiddleware(Middleware):
def after_skip_message(self, broker: Broker, message: Message):
task: Task = message.options["task"]
task.log(str(type(self)), TaskStatus.INFO, "Task has been skipped")
task.log(class_to_path(type(self)), TaskStatus.INFO, "Task has been skipped")
class LoggingMiddleware(Middleware):
@@ -225,6 +230,7 @@ class WorkerStatusMiddleware(Middleware):
sleep(10)
pass
@staticmethod
def keep(status: WorkerStatus):
lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, side_effect=pglock.Raise):

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.8.2 Blueprint schema",
"title": "authentik 2025.8.6 Blueprint schema",
"required": [
"version",
"entries"

View File

@@ -133,6 +133,6 @@ func checkWorker() int {
}
}
log.Debug("successfully checked health")
log.Info("successfully checked health")
return 0
}

View File

@@ -48,7 +48,7 @@ services:
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.6}
ports:
- ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
@@ -72,7 +72,7 @@ services:
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.6}
restart: unless-stopped
user: root
volumes:

11
go.mod
View File

@@ -1,6 +1,6 @@
module goauthentik.io
go 1.24.0
go 1.25.0
require (
beryju.io/ldap v0.1.0
@@ -27,7 +27,7 @@ require (
github.com/sethvargo/go-envconfig v1.3.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/stretchr/testify v1.11.1
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025064.8
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
@@ -77,9 +77,10 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

20
go.sum
View File

@@ -169,8 +169,8 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4=
github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
@@ -189,13 +189,13 @@ goauthentik.io/api/v3 v3.2025064.8 h1:wgegkPUtGSrOR7+Rnd0cxLVU0cEea87BatjESa6BJv
goauthentik.io/api/v3 v3.2025064.8/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab h1:628ME69lBm9C6JY2wXhAph/yjN3jezx1z7BIDLUwxjo=
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
@@ -204,12 +204,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=

View File

@@ -37,13 +37,13 @@ type RedisConfig struct {
}
type ListenConfig struct {
HTTP string `yaml:"listen_http" env:"HTTP, overwrite"`
HTTPS string `yaml:"listen_https" env:"HTTPS, overwrite"`
LDAP string `yaml:"listen_ldap" env:"LDAP, overwrite"`
LDAPS string `yaml:"listen_ldaps" env:"LDAPS, overwrite"`
Radius string `yaml:"listen_radius" env:"RADIUS, overwrite"`
Metrics string `yaml:"listen_metrics" env:"METRICS, overwrite"`
Debug string `yaml:"listen_debug" env:"DEBUG, overwrite"`
HTTP string `yaml:"http" env:"HTTP, overwrite"`
HTTPS string `yaml:"https" env:"HTTPS, overwrite"`
LDAP string `yaml:"ldap" env:"LDAP, overwrite"`
LDAPS string `yaml:"ldaps" env:"LDAPS, overwrite"`
Radius string `yaml:"radius" env:"RADIUS, overwrite"`
Metrics string `yaml:"metrics" env:"METRICS, overwrite"`
Debug string `yaml:"debug" env:"DEBUG, overwrite"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"`
}

View File

@@ -1 +1 @@
2025.8.2
2025.8.6

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),
@@ -99,7 +99,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
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
@@ -180,7 +180,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

@@ -139,7 +139,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
}
@@ -156,7 +156,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
@@ -179,7 +179,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

@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"time"
"beryju.io/ldap"
@@ -50,10 +51,13 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
constants.OCPosixAccount,
constants.OCAKUser,
},
"uidNumber": {pi.GetUserUidNumber(u)},
"gidNumber": {pi.GetUserGidNumber(u)},
"homeDirectory": {fmt.Sprintf("/home/%s", u.Username)},
"sn": {u.Name},
"uidNumber": {pi.GetUserUidNumber(u)},
"gidNumber": {pi.GetUserGidNumber(u)},
"homeDirectory": {fmt.Sprintf("/home/%s", u.Username)},
"sn": {u.Name},
"pwdChangedTime": {u.PasswordChangeDate.In(time.UTC).Format("20060102150405Z")},
"createTimestamp": {u.DateJoined.In(time.UTC).Format("20060102150405Z")},
"modifyTimestamp": {u.LastUpdated.In(time.UTC).Format("20060102150405Z")},
})
return &ldap.Entry{DN: dn, Attributes: attrs}
}

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,
})

View File

@@ -113,7 +113,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
errs.Go(func() error {
if flags.CanSearch {
uapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_user")
searchReq, skip := utils.ParseFilterForUser(c.CoreApi.CoreUsersList(uapisp.Context()).IncludeGroups(true), parsedFilter, false)
searchReq, skip := utils.ParseFilterForUser(c.CoreAPI.CoreUsersList(uapisp.Context()).IncludeGroups(true), parsedFilter, false)
if skip {
req.Log().Trace("Skip backend request")
@@ -132,7 +132,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
} else {
if flags.UserInfo == nil {
uapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_user")
u, _, err := c.CoreApi.CoreUsersRetrieve(uapisp.Context(), flags.UserPk).Execute()
u, _, err := c.CoreAPI.CoreUsersRetrieve(uapisp.Context(), flags.UserPk).Execute()
uapisp.Finish()
if err != nil {
@@ -155,7 +155,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
if needGroups {
errs.Go(func() error {
gapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_group")
searchReq, skip := utils.ParseFilterForGroup(c.CoreApi.CoreGroupsList(gapisp.Context()).IncludeUsers(true).IncludeChildren(true), parsedFilter, false)
searchReq, skip := utils.ParseFilterForGroup(c.CoreAPI.CoreGroupsList(gapisp.Context()).IncludeUsers(true).IncludeChildren(true), parsedFilter, false)
if skip {
req.Log().Trace("Skip backend request")
return nil
@@ -194,7 +194,6 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
}
err = errs.Wait()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err
}

View File

@@ -58,6 +58,7 @@ func (ds *DirectSearcher) SearchSubschema(req *search.Request) (ldap.ServerSearc
"( 1.2.840.113556.1.4.44 NAME 'homeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
"( 1.2.840.113556.1.4.750 NAME 'groupType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
"( 1.2.840.113556.1.4.782 NAME 'objectCategory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )",
"( 1.3.6.1.4.1.42.2.27.8.1.16 NAME 'pwdChangedTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE NO-USER-MODIFICATION )",
"( 1.3.6.1.1.1.1.0 NAME 'uidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.1 NAME 'gidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.12 NAME 'memberUid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )",

View File

@@ -52,12 +52,12 @@ func NewMemorySearcher(si server.LDAPServerInstance, existing search.Searcher) *
func (ms *MemorySearcher) fetch() {
// Error is not handled here, we get an empty/truncated list and the error is logged
users, _ := ak.Paginator(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()).IncludeGroups(true), ak.PaginatorOptions{
users, _ := ak.Paginator(ms.si.GetAPIClient().CoreAPI.CoreUsersList(context.TODO()).IncludeGroups(true), ak.PaginatorOptions{
PageSize: 100,
Logger: ms.log,
})
ms.users = users
groups, _ := ak.Paginator(ms.si.GetAPIClient().CoreApi.CoreGroupsList(context.TODO()).IncludeUsers(true), ak.PaginatorOptions{
groups, _ := ak.Paginator(ms.si.GetAPIClient().CoreAPI.CoreGroupsList(context.TODO()).IncludeUsers(true).IncludeChildren(true), ak.PaginatorOptions{
PageSize: 100,
Logger: ms.log,
})

View File

@@ -121,7 +121,7 @@ func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields
ClientIp: *api.NewNullableString(api.PtrString(r.RemoteAddr)),
Context: fields,
}
_, _, err := a.ak.Client.EventsApi.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
_, _, err := a.ak.Client.EventsAPI.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
if err != nil {
a.log.WithError(err).Warning("failed to report configuration error")
}

View File

@@ -56,7 +56,7 @@ func NewProxyServer(ac *ak.APIController) ak.Outpost {
globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
}
s := &ProxyServer{
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoApi),
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoAPI),
apps: make(map[string]*application.Application),
log: l,
mux: rootMux,

View File

@@ -15,7 +15,7 @@ import (
)
func (ps *ProxyServer) Refresh() error {
providers, err := ak.Paginator(ps.akAPI.Client.OutpostsApi.OutpostsProxyList(context.Background()), ak.PaginatorOptions{
providers, err := ak.Paginator(ps.akAPI.Client.OutpostsAPI.OutpostsProxyList(context.Background()), ak.PaginatorOptions{
PageSize: 100,
Logger: ps.log,
})

View File

@@ -31,7 +31,7 @@ func parseCIDRs(raw string) []*net.IPNet {
}
func (rs *RadiusServer) Refresh() error {
apiProviders, err := ak.Paginator(rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()), ak.PaginatorOptions{
apiProviders, err := ak.Paginator(rs.ac.Client.OutpostsAPI.OutpostsRadiusList(context.Background()), ak.PaginatorOptions{
PageSize: 100,
Logger: rs.log,
})

View File

@@ -32,7 +32,7 @@ func (rs *RadiusServer) Handle_AccessRequest_PAP_Auth(r *RadiusRequest, username
if !passed {
return nil, errors.New("invalid_credentials")
}
access, _, err := fe.ApiClient().OutpostsApi.OutpostsRadiusAccessCheck(
access, _, err := fe.ApiClient().OutpostsAPI.OutpostsRadiusAccessCheck(
r.Context(), r.pi.providerId,
).AppSlug(r.pi.appSlug).Execute()
if err != nil {

View File

@@ -23,7 +23,7 @@ type Watcher struct {
}
func NewWatcher(client *api.APIClient) *Watcher {
cs := ak.NewCryptoStore(client.CryptoApi)
cs := ak.NewCryptoStore(client.CryptoAPI)
l := log.WithField("logger", "authentik.router.brand_tls")
cert, err := crypto.GenerateSelfSignedCert()
if err != nil {
@@ -48,7 +48,7 @@ func (w *Watcher) Start() {
func (w *Watcher) Check() {
w.log.Info("updating brand certificates")
brands, err := ak.Paginator(w.client.CoreApi.CoreBrandsList(context.Background()), ak.PaginatorOptions{
brands, err := ak.Paginator(w.client.CoreAPI.CoreBrandsList(context.Background()), ak.PaginatorOptions{
PageSize: 100,
Logger: w.log,
})

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH

View File

@@ -2,6 +2,10 @@
set -e -o pipefail
MODE_FILE="${TMPDIR}/authentik-mode"
if [[ -z "${PROMETHEUS_MULTIPROC_DIR}" ]]; then
export PROMETHEUS_MULTIPROC_DIR="${TMPDIR:-/tmp}/authentik_prometheus_tmp"
fi
function log {
printf '{"event": "%s", "level": "info", "logger": "bootstrap"}\n' "$@" >/dev/stderr
}
@@ -31,7 +35,7 @@ function check_if_root {
GROUP="authentik:${GROUP_NAME}"
fi
# Fix permissions of certs and media
chown -R authentik:authentik /media /certs
chown -R authentik:authentik /media /certs "${PROMETHEUS_MULTIPROC_DIR}"
chmod ug+rwx /media
chmod ug+rx /certs
exec chpst -u authentik:$GROUP env HOME=/authentik $1
@@ -68,9 +72,6 @@ function prepare_debug {
chown authentik:authentik /unittest.xml
}
if [[ -z "${PROMETHEUS_MULTIPROC_DIR}" ]]; then
export PROMETHEUS_MULTIPROC_DIR="${TMPDIR:-/tmp}/authentik_prometheus_tmp"
fi
mkdir -p "${PROMETHEUS_MULTIPROC_DIR}"
if [[ "$(python -m authentik.lib.config debugger 2>/dev/null)" == "True" ]]; then

View File

@@ -26,7 +26,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2025.8.2
Default: 2025.8.6
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

10
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/authentik",
"version": "2025.8.2",
"version": "2025.8.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/authentik",
"version": "2025.8.2",
"version": "2025.8.6",
"dependencies": {
"@eslint/js": "^9.31.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
@@ -356,6 +356,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz",
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0",
@@ -551,6 +552,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -755,6 +757,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -1409,6 +1412,7 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -1642,6 +1646,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -1690,6 +1695,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/authentik",
"version": "2025.8.2",
"version": "2025.8.6",
"private": true,
"type": "module",
"dependencies": {

View File

@@ -385,7 +385,9 @@ class _PostgresConsumer(Consumer):
processing = len(self.in_processing)
if processing >= self.prefetch:
# Wait and don't consume the message, other worker will be faster
# If we have too many messages already processing, wait and don't consume a message
# straight away, other workers will be faster.
# After waiting consume a message regardless.
self.misses, backoff_ms = compute_backoff(self.misses, max_backoff=1000)
self.logger.debug(
"Too many messages in processing, Sleeping",
@@ -393,7 +395,8 @@ class _PostgresConsumer(Consumer):
backoff_ms=backoff_ms,
)
time.sleep(backoff_ms / 1000)
return None
else:
self.misses = 0
if not self.notifies:
self.notifies += self._poll_for_notify()
@@ -423,6 +426,7 @@ class _PostgresConsumer(Consumer):
self._auto_purge()
self._scheduler()
self.misses = 0
return None
def _purge_locks(self):
@@ -447,7 +451,7 @@ class _PostgresConsumer(Consumer):
self.logger.debug("Running garbage collector")
count = self.query_set.filter(
state__in=(TaskState.DONE, TaskState.REJECTED),
mtime__lte=timezone.now() - timezone.timedelta(seconds=Conf().task_purge_interval),
mtime__lte=timezone.now() - timezone.timedelta(seconds=Conf().task_expiration),
result_expiry__lte=timezone.now(),
).delete()
self.logger.info("Purged messages in all queues", count=count)

View File

@@ -6,10 +6,7 @@ from http.server import HTTPServer as BaseHTTPServer
from ipaddress import IPv6Address, ip_address
from typing import Any
from django.db import (
close_old_connections,
connections,
)
from django.db import DatabaseError, close_old_connections, connections
from dramatiq.actor import Actor
from dramatiq.broker import Broker
from dramatiq.common import current_millis
@@ -132,7 +129,10 @@ class CurrentTask(Middleware):
if f.name not in fields_to_exclude and not f.auto_created and f.column
]
if fields_to_update:
task.save(update_fields=fields_to_update)
try:
task.save(update_fields=fields_to_update)
except DatabaseError:
pass
self._TASKS.set(tasks[:-1])
def after_skip_message(self, broker: Broker, message: Message):

View File

@@ -58,6 +58,7 @@ export function createPrismConfig(overrides = {}) {
"nginx",
"python",
"bash",
"powershell",
],
};

View File

@@ -1,12 +1,12 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.1.1",
"version": "2.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@goauthentik/docusaurus-config",
"version": "2.1.1",
"version": "2.1.2",
"license": "MIT",
"dependencies": {
"deepmerge-ts": "^7.1.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@goauthentik/docusaurus-config",
"version": "2.1.1",
"version": "2.1.2",
"description": "authentik's Docusaurus config",
"license": "MIT",
"scripts": {

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