Compare commits

...

67 Commits

Author SHA1 Message Date
a15a040362 release: 2023.10.5 2023-12-21 14:18:36 +01:00
fcd6dc1d60 events: fix lint (#7700)
* events: fix lint

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

* test without explicit poetry env use?

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

* delete previous poetry env

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

* prevent invalid cached poetry envs

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

* run test-from-stable as matrix and make required

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

* fix missing postgres version

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

* sigh

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

* idk

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	.github/actions/setup/action.yml
#	.github/workflows/ci-main.yml
2023-12-19 18:40:14 +01:00
acc3b59869 events: add better fallback for sanitize_item to ensure everything can be saved as JSON (cherry-pick #7694) (#7937)
events: add better fallback for sanitize_item to ensure everything can be saved as JSON (#7694)

* events: fix events sanitizing not handling all types



* remove some leftover prints



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:31:20 +01:00
d9d5ac10e6 events: include user agent in events (cherry-pick #7693) (#7938)
events: include user agent in events (#7693)

* events: include user agent in events



* fix tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:31:06 +01:00
750669dcab stages/email: improve error handling for incorrect template syntax (cherry-pick #7758) (#7936)
stages/email: improve error handling for incorrect template syntax (#7758)

* stages/email: improve error handling for incorrect template syntax



* add tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:30:56 +01:00
88a3eed67e root: don't show warning when app has no URLs to import (cherry-pick #7765) (#7935)
root: don't show warning when app has no URLs to import (#7765)

Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:30:49 +01:00
6c214fffc4 blueprints: improve file change handler (cherry-pick #7813) (#7934)
blueprints: improve file change handler (#7813)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:30:37 +01:00
70100fc105 web/user: fix search not updating app (cherry-pick #7825) (#7933)
web/user: fix search not updating app (#7825)

web/user: fix app not updating

so when using two classes in a classMap directive, the update fails (basically saying that each class must be separated), however this error only shows when directly calling requestUpdate and is swallowed somewhere when relying on the default render cycle

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:30:23 +01:00
3c1163fabd root: Fix cache related image build issues (cherry-pick #7831) (#7932)
Fix cache related image build issues

Co-authored-by: Philipp Kolberg <philipp.kolberg@t-online.de>
2023-12-19 18:30:15 +01:00
539e8242ff web: fix overflow glitch on ak-page-header (cherry-pick #7883) (#7931)
web: fix overflow glitch on ak-page-header (#7883)

By adding 'grow' but not 'shrink' to the header section, the page was allowed to allocate
as much width as was available when the window opened, but not allowed to resize the width
if it was pushed closed by zoom, page resize, or summon sidebar.

This commit adds 'shrink' to the capabilities of the header.

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2023-12-19 18:30:04 +01:00
2648333590 providers/scim: change familyName default (cherry-pick #7904) (#7930)
providers/scim: change familyName default (#7904)

* Update providers-scim.yaml



* fix: add formatted to match the givenName & familyName



* fix, update tests



---------

Signed-off-by: Antoine <antoine+github@jiveoff.fr>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
Co-authored-by: Antoine <antoine+github@jiveoff.fr>
2023-12-19 18:29:55 +01:00
fe828ef993 tests: fix flaky tests (cherry-pick #7676) (#7939)
tests: fix flaky tests (#7676)

* tests: fix flaky tests



* make test-from-stable use actual latest version



* fix checkout



* remove hardcoded seed



* ignore tests for now i guess idk



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-12-19 18:29:44 +01:00
29a6530742 web: dark/light theme fixes (#7872)
* web: fix css for user tree-view

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

* fix unrelated things

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

* fix header button colors

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

* fix missing fallback not showing default slant

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

* move global theme-dark css to only use for SSR rendered pages

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	.github/workflows/ci-main.yml
#	web/xliff/fr.xlf
2023-12-19 18:18:19 +01:00
a6b9274c4f web/admin: always show oidc well-known URL fields when they're set (#7560)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	web/xliff/de.xlf
#	web/xliff/en.xlf
#	web/xliff/es.xlf
#	web/xliff/fr.xlf
#	web/xliff/pl.xlf
#	web/xliff/pseudo-LOCALE.xlf
#	web/xliff/tr.xlf
#	web/xliff/zh-Hans.xlf
#	web/xliff/zh-Hant.xlf
#	web/xliff/zh_TW.xlf
2023-12-19 18:10:40 +01:00
a2a67161ac release: 2023.10.4 2023-11-21 18:38:24 +01:00
2e8263a99b web: fix locale
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-11-21 18:20:41 +01:00
6b9afed21f security: fix CVE-2023-48228 (cherry-pick #7666) (#7668)
security: fix CVE-2023-48228 (#7666)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-21 18:13:54 +01:00
1eb1f4e0b8 web/admin: fix admins not able to delete MFA devices (#7660)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	web/xliff/zh-Hans.xlf
2023-11-21 15:24:37 +01:00
7c3d60ec3a events: don't update internal service accounts unless needed (cherry-pick #7611) (#7640)
events: stop spam (#7611)

* events: don't log updates to internal service accounts



* dont log reputation updates



* don't actually ignore things, stop updating outpost user when not required



* prevent updating internal service account users



* fix setattr call



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-20 19:43:30 +01:00
a494c6b6e8 root: specify node and python versions in respective config files, deduplicate in CI (#7620)
* root: specify node and python versions in respective config files, deduplicate in CI

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

* fix engines missing for wdio

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

* bump setup python version

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

* actually don't bump a bunch of things

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	poetry.lock
#	website/package.json
2023-11-19 00:35:55 +01:00
6604d3577f core: bump golang from 1.21.3-bookworm to 1.21.4-bookworm (cherry-pick #7483) (#7622)
core: bump golang from 1.21.3-bookworm to 1.21.4-bookworm

Bumps golang from 1.21.3-bookworm to 1.21.4-bookworm.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-19 00:33:07 +01:00
f8bfa7e16a ci: fix permissions for release pipeline to publish binaries (cherry-pick #7512) (#7621)
ci: fix permissions for release pipeline to publish binaries (#7512)

ci: fix permissions

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-19 00:31:20 +01:00
ea6cf6eabf events: fix missing model_* events when not directly authenticated (cherry-pick #7588) (#7597)
events: fix missing model_* events when not directly authenticated (#7588)

* events: fix missing model_* events when not directly authenticated



* defer accessing database



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-16 12:59:41 +01:00
769ce3ce7b providers/scim: fix missing schemas attribute for User and Group (cherry-pick #7477) (#7596)
providers/scim: fix missing schemas attribute for User and Group (#7477)

* providers/scim: fix missing schemas attribute for User and Group



* make things actually work



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-16 12:06:01 +01:00
3891fb3fa8 events: sanitize functions (cherry-pick #7587) (#7589)
events: sanitize functions (#7587)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-15 23:24:13 +01:00
41eb965350 stages/email: use uuid for email confirmation token instead of username (cherry-pick #7581) (#7584)
stages/email: use uuid for email confirmation token instead of username (#7581)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-15 21:57:05 +01:00
8d95612287 providers/proxy: Fix duplicate cookies when using file system store. (cherry-pick #7541) (#7544)
providers/proxy: Fix duplicate cookies when using file system store. (#7541)

Fix duplicate cookies when using file system store.

Co-authored-by: thijs_a <thijs@thijsalders.nl>
2023-11-13 16:02:35 +01:00
82b5274b15 release: 2023.10.3 2023-11-09 18:37:22 +01:00
af56ce3d78 core: fix worker beat toggle inverted (cherry-pick #7508) (#7509)
core: fix worker beat toggle inverted (#7508)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-09 18:36:56 +01:00
f5c6e7aeb0 Web: bugfix: broken backchannel selector (cherry-pick #7480) (#7507)
Web: bugfix: broken backchannel selector (#7480)

* web: break circular dependency between AKElement & Interface.

This commit changes the way the root node of the web application shell is
discovered by child components, such that the base class shared by both
no longer results in a circular dependency between the two models.

I've run this in isolation and have seen no failures of discovery; the identity
token exists as soon as the Interface is constructed and is found by every item
on the page.

* web: fix broken typescript references

This built... and then it didn't?  Anyway, the current fix is to
provide type information the AkInterface for the data that consumers
require.

* web: rollback dependabot's upgrade of context

The most frustrating part of this is that I RAN THIS, dammit, with the updated
context and the current Wizard, and it finished the End-to-End tests without
complaint.

* web: bugfix: broken backchannel selector

There were two bugs here, both of them introduced by me because I didn't understand the
system well enough the first time through, and because I didn't test thoroughly enough.

The first is that I was calling the wrong confirmation code; the resulting syntax survived
because `confirm()` is actually a legitimate function call in the context of the DOM Window,
a legacy survivor similar to `alert()` but with a yes/no return value. Bleah.

The second is that the confirm code doesn't appear to pass back a dictionary with the
`{ items: Array<Provider> }` list, it passes back just the `items` as an Array.

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2023-11-09 17:58:38 +01:00
3809400e93 events: fix gdpr compliance always running (cherry-pick #7491) (#7505)
events: fix gdpr compliance always running

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-11-09 17:57:25 +01:00
1def9865cf web/flows: attempt to fix bitwareden android compatibility (cherry-pick #7455) (#7457)
web/flows: attempt to fix bitwareden android compatibility (#7455)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-06 23:58:44 +01:00
3716298639 sources/oauth: fix patreon (cherry-pick #7454) (#7456)
sources/oauth: fix patreon (#7454)

* web/admin: add note for potentially confusing consumer key/secret



* sources/oauth: fix patreon default scopes



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-06 16:36:22 +01:00
c16317d7cf providers/proxy: fix closed redis client (cherry-pick #7385) (#7429)
providers/proxy: fix closed redis client (#7385)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-03 15:46:17 +01:00
bbb8fa8269 ci: explicitly give write permissions to packages (cherry-pick #7428) (#7430)
ci: explicitly give write permissions to packages (#7428)

* ci: explicitly give write permissions to packages



* run full CI on cherry-picks



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-03 15:46:00 +01:00
e4c251a178 web/admin: fix html error on oauth2 provider page (cherry-pick #7384) (#7424)
web/admin: fix html error on oauth2 provider page (#7384)

* web: break circular dependency between AKElement & Interface.

This commit changes the way the root node of the web application shell is
discovered by child components, such that the base class shared by both
no longer results in a circular dependency between the two models.

I've run this in isolation and have seen no failures of discovery; the identity
token exists as soon as the Interface is constructed and is found by every item
on the page.

* web: fix broken typescript references

This built... and then it didn't?  Anyway, the current fix is to
provide type information the AkInterface for the data that consumers
require.

* \# Details

Extra `>` symbol screwed up the reading of the rest of the component.  Unfortunately,
too many fields in an input are optional, so it was easy for this bug to bypass any
checks by the validators.  I should have caught it myself, though.

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2023-11-03 13:17:26 +01:00
0fefd5f522 stages/email: fix duplicate querystring encoding (cherry-pick #7386) (#7425)
stages/email: fix duplicate querystring encoding (#7386)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-03 13:17:18 +01:00
88057db0b0 providers/oauth2: set auth_via for token and other endpoints (cherry-pick #7417) (#7427)
providers/oauth2: set auth_via for token and other endpoints (#7417)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-11-03 13:17:10 +01:00
91cb6c9beb root: Improve multi arch Docker image build speed (cherry-pick #7355) (#7426)
root: Improve multi arch Docker image build speed (#7355)

* Improve multi arch Docker image build speed

Use only host architecture for GeoIP database update and for Go cross-compilation

* Speedup Go multi-arch compilation for other images

* Speedup multi-arch ldap image build

Co-authored-by: Philipp Kolberg <39984529+PKizzle@users.noreply.github.com>
2023-11-03 13:16:54 +01:00
8e72fcab59 release: 2023.10.2 2023-10-28 21:43:54 +02:00
261879022d security: fix oobe-flow reuse when akadmin is deleted (#7361)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-28 21:24:06 +02:00
2a47ff2977 website/docs: prepare 2023.10.2 release notes (#7362)
website/docs: prepare 2023.10.2

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-28 21:07:57 +02:00
c3a81a1cce website/docs: add missing breaking change due to APPEND_SLASH (#7360)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-28 18:13:56 +02:00
220d739fef lifecycle: rework otp_merge migration (#7359)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-28 17:56:04 +02:00
4a57c6f230 translate: Updates for file web/xliff/en.xlf in zh-Hans (#7354)
Translate web/xliff/en.xlf in zh-Hans

100% translated source file: 'web/xliff/en.xlf'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-10-27 20:41:31 +02:00
4a93b97bec translate: Updates for file web/xliff/en.xlf in zh_CN (#7353)
Translate web/xliff/en.xlf in zh_CN

100% translated source file: 'web/xliff/en.xlf'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-10-27 20:41:17 +02:00
ac2bbd7e2f website/docs: add warning about Helm breaking change in 2024.x (#7351)
Co-authored-by: Jens L. <jens@goauthentik.io>
2023-10-27 17:22:02 +00:00
ad9f500ad1 crypto: fix race conditions when creating self-signed certificates on startup (#7344)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 16:29:10 +02:00
15d7175750 blueprints: fix entries with state: absent not being deleted if their serializer has errors (#7345)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 16:28:56 +02:00
41d372a340 web/admin: fix @change handler for ak-radio elements (#7348)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 16:28:18 +02:00
83b84e8d26 rbac: handle lookup error (#7341)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 13:38:44 +02:00
f22daca091 website/docs: add warning about upgrading to 2023.10 (#7340)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 12:51:49 +02:00
ae4d5a30f2 web/admin: fix role form reacting to enter (#7330)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 11:39:39 +02:00
9708481005 core: bump github.com/google/uuid from 1.3.1 to 1.4.0 (#7333)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-27 11:13:27 +02:00
1c32c9e06d core: bump goauthentik.io/api/v3 from 3.2023083.10 to 3.2023101.1 (#7334)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2023083.10 to 3.2023101.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2023083.10...v3.2023101.1)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-27 11:13:14 +02:00
7a3d92ffdb core: bump ruff from 0.1.2 to 0.1.3 (#7335)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.2...v0.1.3)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-27 11:13:01 +02:00
a72b36d94d core: bump pydantic-scim from 0.0.7 to 0.0.8 (#7336)
Bumps [pydantic-scim](https://chalk.ai) from 0.0.7 to 0.0.8.

---
updated-dependencies:
- dependency-name: pydantic-scim
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-27 11:12:52 +02:00
6b25f6f592 website/blogs: Blog dockers (#7328)
* Dockers blog draft

* redo

* renamed dir

* renamed directory

* added email address

* formatting

* title tweak

* Kens edits

* link

---------

Co-authored-by: Tana Berry <tana@goauthentik.io>
2023-10-26 21:19:14 -05:00
7d91842e8a providers/proxy: attempt to fix duplicate cookie (#7324)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 00:41:13 +02:00
2b4b1d2f76 stages/email: fix sending emails from task (#7325)
closes #7322

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-27 00:39:06 +02:00
2ce5c74f33 web: bump API Client version (#7321)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2023-10-26 21:50:17 +02:00
168fabfc70 Merge branch 'version-2023.10' 2023-10-26 21:20:55 +02:00
eb53c28352 website/docs: update release notes for 2023.10.1 (#7316)
website/docs: update for 2023.10.1

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-26 20:16:07 +02:00
64c38909ff release: 2023.10.1 2023-10-26 20:06:05 +02:00
940492a5e1 lifecycle: fix otp merge migration (#7315)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-10-26 20:05:33 +02:00
134799c734 root: fix pylint errors (#7312) 2023-10-26 19:57:11 +02:00
e086da68cd web: bump API Client version (#7311)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2023-10-26 16:23:15 +00:00
111 changed files with 2912 additions and 2023 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2023.10.0
current_version = 2023.10.5
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)

View File

@ -2,36 +2,39 @@ name: "Setup authentik testing environment"
description: "Setup authentik testing environment"
inputs:
postgresql_tag:
postgresql_version:
description: "Optional postgresql image tag"
default: "12"
runs:
using: "composite"
steps:
- name: Install poetry
- name: Install poetry & deps
shell: bash
run: |
pipx install poetry || true
sudo apt update
sudo apt install -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
- name: Setup python and restore poetry
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version-file: 'pyproject.toml'
cache: "poetry"
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Setup go
uses: actions/setup-go@v4
with:
go-version-file: "go.mod"
- name: Setup dependencies
shell: bash
run: |
export PSQL_TAG=${{ inputs.postgresql_tag }}
export PSQL_TAG=${{ inputs.postgresql_version }}
docker-compose -f .github/actions/setup/docker-compose.yml up -d
poetry env use python3.11
poetry install
cd web && npm ci
- name: Generate config

View File

@ -11,6 +11,7 @@ on:
pull_request:
branches:
- main
- version-*
env:
POSTGRES_DB: authentik
@ -47,25 +48,38 @@ jobs:
- name: run migrations
run: poetry run python -m lifecycle.migrate
test-migrations-from-stable:
name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }}
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
psql:
- 12-alpine
- 15-alpine
- 16-alpine
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup authentik env
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: checkout stable
run: |
# Delete all poetry envs
rm -rf /home/runner/.cache/pypoetry
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
cp -R .github ..
cp -R scripts ..
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
git checkout version/$(python -c "from authentik import __version__; print(__version__)")
rm -rf .github/ scripts/
mv ../.github ../scripts .
- name: Setup authentik env (ensure stable deps are installed)
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: run migrations to stable
run: poetry run python -m lifecycle.migrate
- name: checkout current code
@ -75,9 +89,13 @@ jobs:
git reset --hard HEAD
git clean -d -fx .
git checkout $GITHUB_SHA
# Delete previous poetry env
rm -rf $(poetry env info --path)
poetry install
- name: Setup authentik env (ensure latest deps are installed)
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
- name: migrate to latest
run: poetry run python -m lifecycle.migrate
test-unittest:
@ -96,7 +114,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
with:
postgresql_tag: ${{ matrix.psql }}
postgresql_version: ${{ matrix.psql }}
- name: run unittest
run: |
poetry run make test
@ -185,6 +203,9 @@ jobs:
build:
needs: ci-core-mark
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
@ -235,6 +256,9 @@ jobs:
build-arm64:
needs: ci-core-mark
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
timeout-minutes: 120
steps:
- uses: actions/checkout@v4

View File

@ -9,6 +9,7 @@ on:
pull_request:
branches:
- main
- version-*
jobs:
lint-golint:
@ -65,6 +66,9 @@ jobs:
- ldap
- radius
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
steps:
- uses: actions/checkout@v4
with:
@ -126,7 +130,7 @@ jobs:
go-version-file: "go.mod"
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Generate API

View File

@ -9,6 +9,7 @@ on:
pull_request:
branches:
- main
- version-*
jobs:
lint-eslint:
@ -23,7 +24,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
@ -39,7 +40,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
@ -61,7 +62,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: ${{ matrix.project }}/package.json
cache: "npm"
cache-dependency-path: ${{ matrix.project }}/package-lock.json
- working-directory: ${{ matrix.project }}/
@ -77,7 +78,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/
@ -109,7 +110,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- working-directory: web/

View File

@ -9,6 +9,7 @@ on:
pull_request:
branches:
- main
- version-*
jobs:
lint-prettier:
@ -17,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
@ -31,7 +32,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
@ -52,7 +53,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/

View File

@ -6,6 +6,7 @@ on:
workflow_dispatch:
permissions:
# Needed to be able to push to the next branch
contents: write
jobs:

View File

@ -7,6 +7,9 @@ on:
jobs:
build-server:
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
@ -52,6 +55,9 @@ jobs:
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
build-outpost:
runs-on: ubuntu-latest
permissions:
# Needed to upload contianer images to ghcr.io
packages: write
strategy:
fail-fast: false
matrix:
@ -106,6 +112,9 @@ jobs:
build-outpost-binary:
timeout-minutes: 120
runs-on: ubuntu-latest
permissions:
# Needed to upload binaries to the release
contents: write
strategy:
fail-fast: false
matrix:
@ -122,7 +131,7 @@ jobs:
go-version-file: "go.mod"
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Build web

View File

@ -6,8 +6,8 @@ on:
workflow_dispatch:
permissions:
# Needed to update issues and PRs
issues: write
pull-requests: write
jobs:
stale:

View File

@ -19,7 +19,7 @@ jobs:
token: ${{ steps.generate_token.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: web/package.json
registry-url: "https://registry.npmjs.org"
- name: Generate API Client
run: make gen-client-ts

View File

@ -1,3 +1,5 @@
# syntax=docker/dockerfile:1
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder
@ -7,7 +9,7 @@ WORKDIR /work/website
RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \
--mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \
--mount=type=cache,target=/root/.npm \
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
npm ci --include=dev
COPY ./website /work/website/
@ -25,7 +27,7 @@ WORKDIR /work/web
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
--mount=type=cache,target=/root/.npm \
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
npm ci --include=dev
COPY ./web /work/web/
@ -35,7 +37,14 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build
# Stage 3: Build go proxy
FROM docker.io/golang:1.21.3-bookworm AS go-builder
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS go-builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
ARG GOOS=$TARGETOS
ARG GOARCH=$TARGETARCH
WORKDIR /go/src/goauthentik.io
@ -55,12 +64,12 @@ COPY ./go.sum /go/src/goauthentik.io/go.sum
ENV CGO_ENABLED=0
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /go/authentik ./cmd/server
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server
# Stage 4: MaxMind GeoIP
FROM ghcr.io/maxmind/geoipupdate:v6.0 as geoip
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City"
ENV GEOIPUPDATE_VERBOSE="true"
@ -82,7 +91,9 @@ ENV VENV_PATH="/ak-root/venv" \
POETRY_VIRTUALENVS_CREATE=false \
PATH="/ak-root/venv/bin:$PATH"
RUN --mount=type=cache,target=/var/cache/apt \
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
apt-get update && \
# Required for installing pip packages
apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev

View File

@ -2,7 +2,7 @@
from os import environ
from typing import Optional
__version__ = "2023.10.0"
__version__ = "2023.10.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -21,7 +21,9 @@ _other_urls = []
for _authentik_app in get_apps():
try:
api_urls = import_module(f"{_authentik_app.name}.urls")
except (ModuleNotFoundError, ImportError) as exc:
except ModuleNotFoundError:
continue
except ImportError as exc:
LOGGER.warning("Could not import app's URLs", app_name=_authentik_app.name, exc=exc)
continue
if not hasattr(api_urls, "api_urlpatterns"):

View File

@ -584,12 +584,17 @@ class EntryInvalidError(SentryIgnoredException):
entry_model: Optional[str]
entry_id: Optional[str]
validation_error: Optional[ValidationError]
serializer: Optional[Serializer] = None
def __init__(self, *args: object, validation_error: Optional[ValidationError] = None) -> None:
def __init__(
self, *args: object, validation_error: Optional[ValidationError] = None, **kwargs
) -> None:
super().__init__(*args)
self.entry_model = None
self.entry_id = None
self.validation_error = validation_error
for key, value in kwargs.items():
setattr(self, key, value)
@staticmethod
def from_entry(

View File

@ -255,7 +255,10 @@ class Importer:
try:
full_data = self.__update_pks_for_attrs(entry.get_attrs(self._import))
except ValueError as exc:
raise EntryInvalidError.from_entry(exc, entry) from exc
raise EntryInvalidError.from_entry(
exc,
entry,
) from exc
always_merger.merge(full_data, updated_identifiers)
serializer_kwargs["data"] = full_data
@ -272,6 +275,7 @@ class Importer:
f"Serializer errors {serializer.errors}",
validation_error=exc,
entry=entry,
serializer=serializer,
) from exc
return serializer
@ -300,16 +304,18 @@ class Importer:
)
return False
# Validate each single entry
serializer = None
try:
serializer = self._validate_single(entry)
except EntryInvalidError as exc:
# For deleting objects we don't need the serializer to be valid
if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT:
continue
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
if raise_errors:
raise exc
return False
serializer = exc.serializer
else:
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
if raise_errors:
raise exc
return False
if not serializer:
continue

View File

@ -75,14 +75,14 @@ class BlueprintEventHandler(FileSystemEventHandler):
return
if event.is_directory:
return
root = Path(CONFIG.get("blueprints_dir")).absolute()
path = Path(event.src_path).absolute()
rel_path = str(path.relative_to(root))
if isinstance(event, FileCreatedEvent):
LOGGER.debug("new blueprint file created, starting discovery")
blueprints_discovery.delay()
LOGGER.debug("new blueprint file created, starting discovery", path=rel_path)
blueprints_discovery.delay(rel_path)
if isinstance(event, FileModifiedEvent):
path = Path(event.src_path)
root = Path(CONFIG.get("blueprints_dir")).absolute()
rel_path = str(path.relative_to(root))
for instance in BlueprintInstance.objects.filter(path=rel_path):
for instance in BlueprintInstance.objects.filter(path=rel_path, enabled=True):
LOGGER.debug("modified blueprint file, starting apply", instance=instance)
apply_blueprint.delay(instance.pk.hex)
@ -98,39 +98,32 @@ def blueprints_find_dict():
return blueprints
def blueprints_find():
def blueprints_find() -> list[BlueprintFile]:
"""Find blueprints and return valid ones"""
blueprints = []
root = Path(CONFIG.get("blueprints_dir"))
for path in root.rglob("**/*.yaml"):
rel_path = path.relative_to(root)
# Check if any part in the path starts with a dot and assume a hidden file
if any(part for part in path.parts if part.startswith(".")):
continue
LOGGER.debug("found blueprint", path=str(path))
with open(path, "r", encoding="utf-8") as blueprint_file:
try:
raw_blueprint = load(blueprint_file.read(), BlueprintLoader)
except YAMLError as exc:
raw_blueprint = None
LOGGER.warning("failed to parse blueprint", exc=exc, path=str(path))
LOGGER.warning("failed to parse blueprint", exc=exc, path=str(rel_path))
if not raw_blueprint:
continue
metadata = raw_blueprint.get("metadata", None)
version = raw_blueprint.get("version", 1)
if version != 1:
LOGGER.warning("invalid blueprint version", version=version, path=str(path))
LOGGER.warning("invalid blueprint version", version=version, path=str(rel_path))
continue
file_hash = sha512(path.read_bytes()).hexdigest()
blueprint = BlueprintFile(
str(path.relative_to(root)), version, file_hash, int(path.stat().st_mtime)
)
blueprint = BlueprintFile(str(rel_path), version, file_hash, int(path.stat().st_mtime))
blueprint.meta = from_dict(BlueprintMetadata, metadata) if metadata else None
blueprints.append(blueprint)
LOGGER.debug(
"parsed & loaded blueprint",
hash=file_hash,
path=str(path),
)
return blueprints
@ -138,10 +131,12 @@ def blueprints_find():
throws=(DatabaseError, ProgrammingError, InternalError), base=MonitoredTask, bind=True
)
@prefill_task
def blueprints_discovery(self: MonitoredTask):
def blueprints_discovery(self: MonitoredTask, path: Optional[str] = None):
"""Find blueprints and check if they need to be created in the database"""
count = 0
for blueprint in blueprints_find():
if path and blueprint.path != path:
continue
check_blueprint_v1_file(blueprint)
count += 1
self.set_status(
@ -171,7 +166,11 @@ def check_blueprint_v1_file(blueprint: BlueprintFile):
metadata={},
)
instance.save()
LOGGER.info(
"Creating new blueprint instance from file", instance=instance, path=instance.path
)
if instance.last_applied_hash != blueprint.hash:
LOGGER.info("Applying blueprint due to changed file", instance=instance, path=instance.path)
apply_blueprint.delay(str(instance.pk))

View File

@ -98,6 +98,7 @@ class ApplicationSerializer(ModelSerializer):
class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Application Viewset"""
# pylint: disable=no-member
queryset = Application.objects.all().prefetch_related("provider")
serializer_class = ApplicationSerializer
search_fields = [

View File

@ -139,6 +139,7 @@ class UserAccountSerializer(PassiveSerializer):
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""
# pylint: disable=no-member
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]

View File

@ -38,7 +38,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
managed = ReadOnlyField()
component = SerializerMethodField()
icon = ReadOnlyField(source="get_icon")
icon = ReadOnlyField(source="icon_url")
def get_component(self, obj: Source) -> str:
"""Get object component so that we know how to edit the object"""

View File

@ -171,6 +171,11 @@ class UserSerializer(ModelSerializer):
raise ValidationError("Setting a user to internal service account is not allowed.")
return user_type
def validate(self, attrs: dict) -> dict:
if self.instance and self.instance.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
raise ValidationError("Can't modify internal service account users")
return super().validate(attrs)
class Meta:
model = User
fields = [

View File

@ -17,9 +17,15 @@ class Command(BaseCommand):
"""Run worker"""
def add_arguments(self, parser):
parser.add_argument("-b", "--beat", action="store_true")
parser.add_argument(
"-b",
"--beat",
action="store_false",
help="When set, this worker will _not_ run Beat (scheduled) tasks",
)
def handle(self, **options):
LOGGER.debug("Celery options", **options)
close_old_connections()
if CONFIG.get_bool("remote_debug"):
import debugpy

View File

@ -97,6 +97,7 @@ class SourceFlowManager:
if self.request.user.is_authenticated:
new_connection.user = self.request.user
new_connection = self.update_connection(new_connection, **kwargs)
# pylint: disable=no-member
new_connection.save()
return Action.LINK, new_connection

View File

@ -13,7 +13,6 @@
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
<script src="{% static 'dist/poly.js' %}?version={{ version }}" type="module"></script>
<script src="{% static 'dist/standalone/loading/index.js' %}?version={{ version }}" type="module"></script>

View File

@ -6,6 +6,7 @@
{% block head_before %}
<link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
{% endblock %}

View File

@ -1,13 +1,10 @@
"""authentik crypto app config"""
from datetime import datetime
from typing import TYPE_CHECKING, Optional
from typing import Optional
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.generators import generate_id
if TYPE_CHECKING:
from authentik.crypto.models import CertificateKeyPair
MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"
@ -23,33 +20,37 @@ class AuthentikCryptoConfig(ManagedAppConfig):
"""Load crypto tasks"""
self.import_module("authentik.crypto.tasks")
def _create_update_cert(self, cert: Optional["CertificateKeyPair"] = None):
def _create_update_cert(self):
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
builder = CertificateBuilder("authentik Internal JWT Certificate")
common_name = "authentik Internal JWT Certificate"
builder = CertificateBuilder(common_name)
builder.build(
subject_alt_names=["goauthentik.io"],
validity_days=360,
)
if not cert:
cert = CertificateKeyPair()
builder.cert = cert
builder.cert.managed = MANAGED_KEY
builder.save()
CertificateKeyPair.objects.update_or_create(
managed=MANAGED_KEY,
defaults={
"name": common_name,
"certificate_data": builder.certificate,
"key_data": builder.private_key,
},
)
def reconcile_managed_jwt_cert(self):
"""Ensure managed JWT certificate"""
from authentik.crypto.models import CertificateKeyPair
certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY)
if not certs.exists():
self._create_update_cert()
return
cert: CertificateKeyPair = certs.first()
cert: Optional[CertificateKeyPair] = CertificateKeyPair.objects.filter(
managed=MANAGED_KEY
).first()
now = datetime.now()
if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after:
self._create_update_cert(cert)
if not cert or (
now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after
):
self._create_update_cert()
def reconcile_self_signed(self):
"""Create self-signed keypair"""
@ -61,4 +62,10 @@ class AuthentikCryptoConfig(ManagedAppConfig):
return
builder = CertificateBuilder(name)
builder.build(subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"])
builder.save()
CertificateKeyPair.objects.get_or_create(
name=name,
defaults={
"certificate_data": builder.certificate,
"key_data": builder.private_key,
},
)

View File

@ -27,6 +27,7 @@ from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
from authentik.providers.scim.models import SCIMGroup, SCIMUser
from authentik.stages.authenticator_static.models import StaticToken
@ -52,11 +53,13 @@ IGNORED_MODELS = (
RefreshToken,
SCIMUser,
SCIMGroup,
Reputation,
)
def should_log_model(model: Model) -> bool:
"""Return true if operation on `model` should be logged"""
# Check for silk by string so this comparison doesn't fail when silk isn't installed
if model.__module__.startswith("silk"):
return False
return model.__class__ not in IGNORED_MODELS
@ -93,21 +96,30 @@ class AuditMiddleware:
of models"""
get_response: Callable[[HttpRequest], HttpResponse]
anonymous_user: User = None
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
def _ensure_fallback_user(self):
"""Defer fetching anonymous user until we have to"""
if self.anonymous_user:
return
from guardian.shortcuts import get_anonymous_user
self.anonymous_user = get_anonymous_user()
def connect(self, request: HttpRequest):
"""Connect signal for automatic logging"""
if not hasattr(request, "user"):
return
if not getattr(request.user, "is_authenticated", False):
return
self._ensure_fallback_user()
user = getattr(request, "user", self.anonymous_user)
if not user.is_authenticated:
user = self.anonymous_user
if not hasattr(request, "request_id"):
return
post_save_handler = partial(self.post_save_handler, user=request.user, request=request)
pre_delete_handler = partial(self.pre_delete_handler, user=request.user, request=request)
m2m_changed_handler = partial(self.m2m_changed_handler, user=request.user, request=request)
post_save_handler = partial(self.post_save_handler, user=user, request=request)
pre_delete_handler = partial(self.pre_delete_handler, user=user, request=request)
m2m_changed_handler = partial(self.m2m_changed_handler, user=user, request=request)
post_save.connect(
post_save_handler,
dispatch_uid=request.request_id,

View File

@ -217,6 +217,7 @@ class Event(SerializerModel, ExpiringModel):
"path": request.path,
"method": request.method,
"args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))),
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
}
# Special case for events created during flow execution
# since they keep the http query within a wrapped query

View File

@ -13,6 +13,7 @@ from authentik.events.tasks import event_notification_handler, gdpr_cleanup
from authentik.flows.models import Stage
from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.config import CONFIG
from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
@ -92,4 +93,5 @@ def event_post_save_notification(sender, instance: Event, **_):
@receiver(pre_delete, sender=User)
def event_user_pre_delete_cleanup(sender, instance: User, **_):
"""If gdpr_compliance is enabled, remove all the user's events"""
gdpr_cleanup.delay(instance.pk)
if CONFIG.get_bool("gdpr_compliance", True):
gdpr_cleanup.delay(instance.pk)

View File

@ -53,7 +53,15 @@ class TestEvents(TestCase):
"""Test plain from_http"""
event = Event.new("unittest").from_http(self.factory.get("/"))
self.assertEqual(
event.context, {"http_request": {"args": {}, "method": "GET", "path": "/"}}
event.context,
{
"http_request": {
"args": {},
"method": "GET",
"path": "/",
"user_agent": "",
}
},
)
def test_from_http_clean_querystring(self):
@ -67,6 +75,7 @@ class TestEvents(TestCase):
"args": {"token": SafeExceptionReporterFilter.cleansed_substitute},
"method": "GET",
"path": "/",
"user_agent": "",
}
},
)
@ -83,6 +92,7 @@ class TestEvents(TestCase):
"args": {"token": SafeExceptionReporterFilter.cleansed_substitute},
"method": "GET",
"path": "/",
"user_agent": "",
}
},
)

View File

@ -5,12 +5,13 @@ from dataclasses import asdict, is_dataclass
from datetime import date, datetime, time, timedelta
from enum import Enum
from pathlib import Path
from types import GeneratorType
from types import GeneratorType, NoneType
from typing import Any, Optional
from uuid import UUID
from django.contrib.auth.models import AnonymousUser
from django.core.handlers.wsgi import WSGIRequest
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.db.models.base import Model
from django.http.request import HttpRequest
@ -153,7 +154,20 @@ def sanitize_item(value: Any) -> Any:
return value.isoformat()
if isinstance(value, timedelta):
return str(value.total_seconds())
return value
if callable(value):
return {
"type": "callable",
"name": value.__name__,
"module": value.__module__,
}
# List taken from the stdlib's JSON encoder (_make_iterencode, encoder.py:415)
if isinstance(value, (bool, int, float, NoneType, list, tuple, dict)):
return value
try:
return DjangoJSONEncoder().default(value)
except TypeError:
return str(value)
return str(value)
def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:

View File

@ -0,0 +1,34 @@
# Generated by Django 4.2.6 on 2023-10-28 14:24
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def set_oobe_flow_authentication(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from guardian.shortcuts import get_anonymous_user
Flow = apps.get_model("authentik_flows", "Flow")
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
users = User.objects.using(db_alias).exclude(username="akadmin")
try:
users = users.exclude(pk=get_anonymous_user().pk)
# pylint: disable=broad-except
except Exception: # nosec
pass
if users.exists():
Flow.objects.filter(slug="initial-setup").update(authentication="require_superuser")
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0026_alter_flow_options"),
]
operations = [
migrations.RunPython(set_oobe_flow_authentication),
]

View File

@ -167,7 +167,11 @@ class ChallengeStageView(StageView):
stage_type=self.__class__.__name__, method="get_challenge"
).time(),
):
challenge = self.get_challenge(*args, **kwargs)
try:
challenge = self.get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
with Hub.current.start_span(
op="authentik.flow.stage._get_challenge",
description=self.__class__.__name__,

View File

@ -344,12 +344,22 @@ class Outpost(SerializerModel, ManagedModel):
user_created = False
if not user:
user: User = User.objects.create(username=self.user_identifier)
user.set_unusable_password()
user_created = True
user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
user.name = f"Outpost {self.name} Service-Account"
user.path = USER_PATH_OUTPOSTS
user.save()
attrs = {
"type": UserTypes.INTERNAL_SERVICE_ACCOUNT,
"name": f"Outpost {self.name} Service-Account",
"path": USER_PATH_OUTPOSTS,
}
dirty = False
for key, value in attrs.items():
if getattr(user, key) != value:
dirty = True
setattr(user, key, value)
if user.has_usable_password():
user.set_unusable_password()
dirty = True
if dirty:
user.save()
if user_created:
self.build_user_permissions(user)
return user

View File

@ -0,0 +1,187 @@
"""Test token view"""
from base64 import b64encode, urlsafe_b64encode
from hashlib import sha256
from django.test import RequestFactory
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.challenge import ChallengeTypes
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.constants import GRANT_TYPE_AUTHORIZATION_CODE
from authentik.providers.oauth2.models import AuthorizationCode, OAuth2Provider
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestTokenPKCE(OAuthTestCase):
"""Test token view"""
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.app = Application.objects.create(name=generate_id(), slug="test")
def test_pkce_missing_in_token(self):
"""Test full with pkce"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="foo://localhost",
access_code_validity="seconds=100",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
challenge = generate_id()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"code_challenge": challenge,
"code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": f"foo://localhost?code={code.code}&state={state}",
},
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
# Missing the code_verifier here
"redirect_uri": "foo://localhost",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertJSONEqual(
response.content,
{"error": "invalid_request", "error_description": "The request is otherwise malformed"},
)
self.assertEqual(response.status_code, 400)
def test_pkce_correct_s256(self):
"""Test full with pkce"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="foo://localhost",
access_code_validity="seconds=100",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
verifier = generate_id()
challenge = (
urlsafe_b64encode(sha256(verifier.encode("ascii")).digest())
.decode("utf-8")
.replace("=", "")
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"code_challenge": challenge,
"code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": f"foo://localhost?code={code.code}&state={state}",
},
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"code_verifier": verifier,
"redirect_uri": "foo://localhost",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 200)
def test_pkce_correct_plain(self):
"""Test full with pkce"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="foo://localhost",
access_code_validity="seconds=100",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
verifier = generate_id()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"code_challenge": verifier,
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": f"foo://localhost?code={code.code}&state={state}",
},
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"code_verifier": verifier,
"redirect_uri": "foo://localhost",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 200)

View File

@ -188,6 +188,7 @@ def authenticate_provider(request: HttpRequest) -> Optional[OAuth2Provider]:
if client_id != provider.client_id or client_secret != provider.client_secret:
LOGGER.debug("(basic) Provider for basic auth does not exist")
return None
CTX_AUTH_VIA.set("oauth_client_secret")
return provider

View File

@ -17,6 +17,7 @@ from jwt import PyJWK, PyJWT, PyJWTError, decode
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import (
USER_ATTRIBUTE_EXPIRES,
USER_ATTRIBUTE_GENERATED,
@ -221,7 +222,10 @@ class TokenParams:
raise TokenError("invalid_grant")
# Validate PKCE parameters.
if self.code_verifier:
if self.authorization_code.code_challenge:
# Authorization code had PKCE but we didn't get one
if not self.code_verifier:
raise TokenError("invalid_request")
if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
new_code_challenge = (
urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
@ -448,6 +452,7 @@ class TokenView(View):
if not self.provider:
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
raise TokenError("invalid_client")
CTX_AUTH_VIA.set("oauth_client_secret")
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
with Hub.current.start_span(

View File

@ -46,7 +46,9 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
def to_scim(self, obj: Group) -> SCIMGroupSchema:
"""Convert authentik user into SCIM"""
raw_scim_group = {}
raw_scim_group = {
"schemas": ("urn:ietf:params:scim:schemas:core:2.0:Group",),
}
for mapping in (
self.provider.property_mappings_group.all().order_by("name").select_subclasses()
):

View File

@ -15,12 +15,14 @@ from pydanticscim.user import User as BaseUser
class User(BaseUser):
"""Modified User schema with added externalId field"""
schemas: tuple[str] = ("urn:ietf:params:scim:schemas:core:2.0:User",)
externalId: Optional[str] = None
class Group(BaseGroup):
"""Modified Group schema with added externalId field"""
schemas: tuple[str] = ("urn:ietf:params:scim:schemas:core:2.0:Group",)
externalId: Optional[str] = None

View File

@ -39,7 +39,9 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]):
def to_scim(self, obj: User) -> SCIMUserSchema:
"""Convert authentik user into SCIM"""
raw_scim_user = {}
raw_scim_user = {
"schemas": ("urn:ietf:params:scim:schemas:core:2.0:User",),
}
for mapping in self.provider.property_mappings.all().order_by("name").select_subclasses():
if not isinstance(mapping, SCIMMapping):
continue

View File

@ -61,7 +61,11 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[1].method, "POST")
self.assertJSONEqual(
mock.request_history[1].body,
{"externalId": str(group.pk), "displayName": group.name},
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
@Mocker()
@ -96,7 +100,11 @@ class SCIMGroupTests(TestCase):
validate(body, loads(schema.read()))
self.assertEqual(
body,
{"externalId": str(group.pk), "displayName": group.name},
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
group.save()
self.assertEqual(mock.call_count, 4)
@ -129,7 +137,11 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[1].method, "POST")
self.assertJSONEqual(
mock.request_history[1].body,
{"externalId": str(group.pk), "displayName": group.name},
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
group.delete()
self.assertEqual(mock.call_count, 4)

View File

@ -89,17 +89,22 @@ class SCIMMembershipTests(TestCase):
self.assertJSONEqual(
mocker.request_history[3].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"emails": [],
"active": True,
"externalId": user.uid,
"name": {"familyName": "", "formatted": "", "givenName": ""},
"name": {"familyName": " ", "formatted": " ", "givenName": ""},
"displayName": "",
"userName": user.username,
},
)
self.assertJSONEqual(
mocker.request_history[5].body,
{"externalId": str(group.pk), "displayName": group.name},
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
with Mocker() as mocker:
@ -118,6 +123,7 @@ class SCIMMembershipTests(TestCase):
self.assertJSONEqual(
mocker.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "add",
@ -125,7 +131,6 @@ class SCIMMembershipTests(TestCase):
"value": [{"value": user_scim_id}],
}
],
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
},
)
@ -174,17 +179,22 @@ class SCIMMembershipTests(TestCase):
self.assertJSONEqual(
mocker.request_history[3].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"displayName": "",
"emails": [],
"externalId": user.uid,
"name": {"familyName": "", "formatted": "", "givenName": ""},
"name": {"familyName": " ", "formatted": " ", "givenName": ""},
"userName": user.username,
},
)
self.assertJSONEqual(
mocker.request_history[5].body,
{"externalId": str(group.pk), "displayName": group.name},
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
with Mocker() as mocker:
@ -203,6 +213,7 @@ class SCIMMembershipTests(TestCase):
self.assertJSONEqual(
mocker.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "add",
@ -210,7 +221,6 @@ class SCIMMembershipTests(TestCase):
"value": [{"value": user_scim_id}],
}
],
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
},
)
@ -230,6 +240,7 @@ class SCIMMembershipTests(TestCase):
self.assertJSONEqual(
mocker.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "remove",
@ -237,6 +248,5 @@ class SCIMMembershipTests(TestCase):
"value": [{"value": user_scim_id}],
}
],
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
},
)

View File

@ -57,7 +57,7 @@ class SCIMUserTests(TestCase):
uid = generate_id()
user = User.objects.create(
username=uid,
name=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
@ -66,6 +66,7 @@ class SCIMUserTests(TestCase):
self.assertJSONEqual(
mock.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
@ -76,11 +77,11 @@ class SCIMUserTests(TestCase):
],
"externalId": user.uid,
"name": {
"familyName": "",
"formatted": uid,
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"displayName": uid,
"displayName": f"{uid} {uid}",
"userName": uid,
},
)
@ -109,7 +110,7 @@ class SCIMUserTests(TestCase):
uid = generate_id()
user = User.objects.create(
username=uid,
name=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
@ -121,6 +122,7 @@ class SCIMUserTests(TestCase):
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
@ -129,11 +131,11 @@ class SCIMUserTests(TestCase):
"value": f"{uid}@goauthentik.io",
}
],
"displayName": uid,
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": "",
"formatted": uid,
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
@ -164,7 +166,7 @@ class SCIMUserTests(TestCase):
uid = generate_id()
user = User.objects.create(
username=uid,
name=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
@ -173,6 +175,7 @@ class SCIMUserTests(TestCase):
self.assertJSONEqual(
mock.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
@ -183,11 +186,11 @@ class SCIMUserTests(TestCase):
],
"externalId": user.uid,
"name": {
"familyName": "",
"formatted": uid,
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"displayName": uid,
"displayName": f"{uid} {uid}",
"userName": uid,
},
)
@ -227,7 +230,7 @@ class SCIMUserTests(TestCase):
)
user = User.objects.create(
username=uid,
name=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
@ -240,6 +243,7 @@ class SCIMUserTests(TestCase):
self.assertJSONEqual(
mock.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
@ -250,11 +254,11 @@ class SCIMUserTests(TestCase):
],
"externalId": user.uid,
"name": {
"familyName": "",
"formatted": uid,
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"displayName": uid,
"displayName": f"{uid} {uid}",
"userName": uid,
},
)

View File

@ -32,13 +32,19 @@ class PermissionSerializer(ModelSerializer):
def get_app_label_verbose(self, instance: Permission) -> str:
"""Human-readable app label"""
return apps.get_app_config(instance.content_type.app_label).verbose_name
try:
return apps.get_app_config(instance.content_type.app_label).verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"
def get_model_verbose(self, instance: Permission) -> str:
"""Human-readable model name"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"
class Meta:
model = Permission

View File

@ -28,9 +28,12 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer):
def get_model_verbose(self, instance: GroupObjectPermission) -> str:
"""Get model label from permission's model"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"
def get_object_description(self, instance: GroupObjectPermission) -> Optional[str]:
"""Get model description from attached model. This operation takes at least
@ -38,7 +41,10 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer):
view_ permission on the object"""
app_label = instance.content_type.app_label
model = instance.content_type.model
model_class = apps.get_model(app_label, model)
try:
model_class = apps.get_model(app_label, model)
except LookupError:
return None
objects = get_objects_for_group(instance.group, f"{app_label}.view_{model}", model_class)
obj = objects.first()
if not obj:

View File

@ -28,9 +28,12 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer):
def get_model_verbose(self, instance: UserObjectPermission) -> str:
"""Get model label from permission's model"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"
def get_object_description(self, instance: UserObjectPermission) -> Optional[str]:
"""Get model description from attached model. This operation takes at least
@ -38,7 +41,10 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer):
view_ permission on the object"""
app_label = instance.content_type.app_label
model = instance.content_type.model
model_class = apps.get_model(app_label, model)
try:
model_class = apps.get_model(app_label, model)
except LookupError:
return None
objects = get_objects_for_user(instance.user, f"{app_label}.view_{model}", model_class)
obj = objects.first()
if not obj:

View File

@ -12,8 +12,9 @@ class PatreonOAuthRedirect(OAuthRedirect):
"""Patreon OAuth2 Redirect"""
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
# https://docs.patreon.com/#scopes
return {
"scope": ["openid", "email", "profile"],
"scope": ["identity", "identity[email]"],
}

View File

@ -69,7 +69,6 @@ class AuthenticatorSMSStageView(ChallengeStageView):
stage: AuthenticatorSMSStage = self.executor.current_stage
hashed_number = hash_phone_number(phone_number)
query = Q(phone_number=hashed_number) | Q(phone_number=phone_number)
print(SMSDevice.objects.filter(query, stage=stage.pk))
if SMSDevice.objects.filter(query, stage=stage.pk).exists():
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message

View File

@ -199,11 +199,9 @@ class AuthenticatorSMSStageTests(FlowTestCase):
sms_send_mock,
),
):
print(self.client.session[SESSION_KEY_PLAN])
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
print(response.content.decode())
self.assertStageResponse(
response,
self.flow,

View File

@ -184,6 +184,7 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase):
"args": {},
"method": "GET",
"path": f"/api/v3/flows/executor/{flow.slug}/",
"user_agent": "",
},
},
)

View File

@ -1,9 +1,11 @@
"""authentik multi-stage authentication engine"""
from datetime import timedelta
from uuid import uuid4
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.template.exceptions import TemplateSyntaxError
from django.urls import reverse
from django.utils.text import slugify
from django.utils.timezone import now
@ -11,11 +13,14 @@ from django.utils.translation import gettext as _
from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.exceptions import StageInvalidException
from authentik.flows.models import FlowDesignation, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -52,16 +57,11 @@ class EmailStageView(ChallengeStageView):
kwargs={"flow_slug": self.executor.flow.slug},
)
# Parse query string from current URL (full query string)
query_params = QueryDict(self.request.META.get("QUERY_STRING", ""), mutable=True)
# this view is only run within a flow executor, where we need to get the query string
# from the query= parameter (double encoded); but for the redirect
# we need to expand it since it'll go through the flow interface
query_params = QueryDict(self.request.GET.get(QS_QUERY), mutable=True)
query_params.pop(QS_KEY_TOKEN, None)
# Check for nested query string used by flow executor, and remove any
# kind of flow token from that
if QS_QUERY in query_params:
inner_query_params = QueryDict(query_params.get(QS_QUERY), mutable=True)
inner_query_params.pop(QS_KEY_TOKEN, None)
query_params[QS_QUERY] = inner_query_params.urlencode()
query_params.update(kwargs)
full_url = base_url
if len(query_params) > 0:
@ -75,7 +75,7 @@ class EmailStageView(ChallengeStageView):
valid_delta = timedelta(
minutes=current_stage.token_expiry + 1
) # + 1 because django timesince always rounds down
identifier = slugify(f"ak-email-stage-{current_stage.name}-{pending_user}")
identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists
tokens = FlowToken.objects.filter(identifier=identifier)
if not tokens.exists():
@ -107,18 +107,27 @@ class EmailStageView(ChallengeStageView):
current_stage: EmailStage = self.executor.current_stage
token = self.get_token()
# Send mail to user
message = TemplateEmailMessage(
subject=_(current_stage.subject),
to=[email],
language=pending_user.locale(self.request),
template_name=current_stage.template,
template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user,
"expires": token.expires,
},
)
send_mails(current_stage, message)
try:
message = TemplateEmailMessage(
subject=_(current_stage.subject),
to=[email],
language=pending_user.locale(self.request),
template_name=current_stage.template,
template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user,
"expires": token.expires,
},
)
send_mails(current_stage, message)
except TemplateSyntaxError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=current_stage.template,
).from_http(self.request)
raise StageInvalidException from exc
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify
@ -139,7 +148,11 @@ class EmailStageView(ChallengeStageView):
return self.executor.stage_invalid()
# Check if we've already sent the initial e-mail
if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context:
self.send_email()
try:
self.send_email()
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True
return super().get(request, *args, **kwargs)

View File

@ -13,6 +13,7 @@ from authentik.events.models import Event, EventAction
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
from authentik.stages.email.models import EmailStage
from authentik.stages.email.utils import logo_data
LOGGER = get_logger()
@ -81,6 +82,10 @@ def send_mail(self: MonitoredTask, message: dict[Any, Any], email_stage_pk: Opti
# Because we use the Message-ID as UID for the task, manually assign it
message_object.extra_headers["Message-ID"] = message_id
# Add the logo (we can't add it in the previous message since MIMEImage
# can't be converted to json)
message_object.attach(logo_data())
LOGGER.debug("Sending mail", to=message_object.to)
backend.send_messages([message_object])
Event.new(

View File

@ -259,7 +259,7 @@ class TestEmailStage(FlowTestCase):
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
url += "?foo=bar"
url += "?query=" + urlencode({"foo": "bar"})
request = self.factory.get(url)
stage_view = EmailStageView(
FlowExecutorView(
@ -273,31 +273,3 @@ class TestEmailStage(FlowTestCase):
stage_view.get_full_url(**{QS_KEY_TOKEN: token}),
f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}",
)
def test_url_existing_params_nested(self):
"""Test to ensure that URL params are preserved in the URL being sent (including nested)"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
url += "?foo=bar&"
url += "query=" + urlencode({"nested": "value"})
request = self.factory.get(url)
stage_view = EmailStageView(
FlowExecutorView(
request=request,
flow=self.flow,
),
request=request,
)
token = generate_id()
self.assertEqual(
stage_view.get_full_url(**{QS_KEY_TOKEN: token}),
(
f"http://testserver/if/flow/{self.flow.slug}"
f"/?foo=bar&query=nested%3Dvalue&flow_token={token}"
),
)

View File

@ -4,11 +4,20 @@ from pathlib import Path
from shutil import rmtree
from tempfile import mkdtemp, mkstemp
from typing import Any
from unittest.mock import PropertyMock, patch
from django.conf import settings
from django.test import TestCase
from django.core.mail.backends.locmem import EmailBackend
from django.urls import reverse
from authentik.stages.email.models import get_template_choices
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage, get_template_choices
def get_templates_setting(temp_dir: str) -> dict[str, Any]:
@ -18,11 +27,18 @@ def get_templates_setting(temp_dir: str) -> dict[str, Any]:
return templates_setting
class TestEmailStageTemplates(TestCase):
class TestEmailStageTemplates(FlowTestCase):
"""Email tests"""
def setUp(self) -> None:
self.dir = mkdtemp()
self.dir = Path(mkdtemp())
self.user = create_test_admin_user()
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
self.stage = EmailStage.objects.create(
name="email",
)
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
def tearDown(self) -> None:
rmtree(self.dir)
@ -38,3 +54,37 @@ class TestEmailStageTemplates(TestCase):
self.assertEqual(len(choices), 3)
unlink(file)
unlink(file2)
def test_custom_template_invalid_syntax(self):
"""Test with custom template"""
with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid:
_invalid.write("{% blocktranslate %}")
with self.settings(TEMPLATES=get_templates_setting(self.dir)):
self.stage.template = "invalid.html"
plan = FlowPlan(
flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
)
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
with patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
error_message="Unknown error",
)
events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR)
self.assertEqual(len(events), 1)
event = events.first()
self.assertEqual(
event.context["message"], "Exception occurred while rendering E-mail template"
)
self.assertEqual(event.context["template"], "invalid.html")

View File

@ -9,7 +9,7 @@ from django.utils import translation
@lru_cache()
def logo_data():
def logo_data() -> MIMEImage:
"""Get logo as MIME Image for emails"""
path = Path("web/icons/icon_left_brand.png")
if not path.exists():
@ -29,5 +29,4 @@ class TemplateEmailMessage(EmailMultiAlternatives):
super().__init__(**kwargs)
self.content_subtype = "html"
self.mixed_subtype = "related"
self.attach(logo_data())
self.attach_alternative(html_content, "text/html")

View File

@ -6,6 +6,7 @@ from django.urls import reverse
from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
@ -58,11 +59,33 @@ class TestUserWriteStage(FlowTestCase):
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"])
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(password))
self.assertEqual(
list(user_qs.first().ak_groups.order_by("name")), [self.other_group, self.group]
user = user_qs.first()
self.assertTrue(user.check_password(password))
self.assertEqual(list(user.ak_groups.order_by("name")), [self.other_group, self.group])
self.assertEqual(user.attributes, {USER_ATTRIBUTE_SOURCES: [self.source.name]})
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED,
context__model={
"app": "authentik_core",
"model_name": "user",
"pk": user.pk,
"name": "name",
},
)
)
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_UPDATED,
context__model={
"app": "authentik_core",
"model_name": "user",
"pk": user.pk,
"name": "name",
},
)
)
self.assertEqual(user_qs.first().attributes, {USER_ATTRIBUTE_SOURCES: [self.source.name]})
def test_user_update(self):
"""Test update of existing user"""

View File

@ -85,6 +85,19 @@ entries:
identifiers:
name: default-oobe-password-usable
model: authentik_policies_expression.expressionpolicy
- attrs:
expression: |
# This policy ensures that the setup flow can only be
# used one time
from authentik.flows.models import Flow, FlowAuthenticationRequirement
Flow.objects.filter(slug="initial-setup").update(
authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER,
)
return True
id: policy-default-oobe-flow-set-authentication
identifiers:
name: default-oobe-flow-set-authentication
model: authentik_policies_expression.expressionpolicy
- attrs:
fields:
- !KeyOf prompt-field-header
@ -129,6 +142,7 @@ entries:
evaluate_on_plan: true
invalid_response_action: retry
re_evaluate_policies: false
id: binding-login
identifiers:
order: 100
stage: !KeyOf stage-default-authentication-login
@ -144,3 +158,8 @@ entries:
policy: !KeyOf policy-default-oobe-prefill-user
target: !KeyOf binding-password-write
model: authentik_policies.policybinding
- identifiers:
order: 0
policy: !KeyOf policy-default-oobe-flow-set-authentication
target: !KeyOf binding-login
model: authentik_policies.policybinding

View File

@ -42,9 +42,3 @@ entries:
user: !KeyOf admin-user
attrs:
key: !Context token
- model: authentik_blueprints.blueprintinstance
identifiers:
metadata:
labels:
blueprints.goauthentik.io/system-bootstrap: "true"
state: absent

View File

@ -11,13 +11,15 @@ entries:
name: "authentik default SCIM Mapping: User"
expression: |
# Some implementations require givenName and familyName to be set
givenName, familyName = request.user.name, ""
givenName, familyName = request.user.name, " "
formatted = request.user.name + " "
# This default sets givenName to the name before the first space
# and the remainder as family name
# if the user's name has no space the givenName is the entire name
# (this might cause issues with some SCIM implementations)
if " " in request.user.name:
givenName, _, familyName = request.user.name.partition(" ")
formatted = request.user.name
# photos supports URLs to images, however authentik might return data URIs
avatar = request.user.avatar
@ -39,7 +41,7 @@ entries:
return {
"userName": request.user.username,
"name": {
"formatted": request.user.name,
"formatted": formatted,
"givenName": givenName,
"familyName": familyName,
},

View File

@ -32,7 +32,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
restart: unless-stopped
command: server
environment:
@ -53,7 +53,7 @@ services:
- postgresql
- redis
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5}
restart: unless-stopped
command: worker
environment:

4
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/go-openapi/runtime v0.26.0
github.com/go-openapi/strfmt v0.21.7
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.1
github.com/google/uuid v1.4.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/securecookie v1.1.1
@ -27,7 +27,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
goauthentik.io/api/v3 v3.2023083.10
goauthentik.io/api/v3 v3.2023101.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.4.0

7
go.sum
View File

@ -211,8 +211,9 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@ -355,8 +356,8 @@ go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyK
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2023083.10 h1:mMCOfsqjouSSxedSkCK4k0Cwtt68CWzQgR7Um6ooOQs=
goauthentik.io/api/v3 v3.2023083.10/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2023101.1 h1:KIQ4wmxjE+geAVB0wBfmxW9Uzo/tA0dbd2hSUJ7YJ3M=
goauthentik.io/api/v3 v3.2023101.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2023.10.0"
const VERSION = "2023.10.5"

View File

@ -50,7 +50,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
Domain: *p.CookieDomain,
SameSite: http.SameSiteLaxMode,
MaxAge: maxAge,
Path: externalHost.Path,
Path: "/",
})
a.log.Trace("using redis session backend")
@ -71,7 +71,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
cs.Options.Domain = *p.CookieDomain
cs.Options.SameSite = http.SameSiteLaxMode
cs.Options.MaxAge = maxAge
cs.Options.Path = externalHost.Path
cs.Options.Path = "/"
a.log.WithField("dir", dir).Trace("using filesystem session backend")
return cs
}
@ -131,7 +131,6 @@ func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) er
}
if rs, ok := a.sessions.(*redisstore.RedisStore); ok {
client := rs.Client()
defer client.Close()
keys, err := client.Keys(ctx, fmt.Sprintf("%s*", RedisKeyPrefix)).Result()
if err != nil {
return err

View File

@ -1,5 +1,14 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM docker.io/golang:1.21.3-bookworm AS builder
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
ARG GOOS=$TARGETOS
ARG GOARCH=$TARGETARCH
WORKDIR /go/src/goauthentik.io
@ -11,9 +20,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
ENV CGO_ENABLED=0
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /go/ldap ./cmd/ldap
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
GOARM="${TARGETVARIANT#v}" go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM gcr.io/distroless/static-debian11:debug

View File

@ -2,16 +2,22 @@
from lifecycle.migrate import BaseMigration
SQL_STATEMENT = """
DELETE FROM django_migrations WHERE app = 'otp_static';
DELETE FROM django_migrations WHERE app = 'otp_totp';
BEGIN TRANSACTION;
-- Update migrations (static)
UPDATE django_migrations SET app = 'authentik_stages_authenticator_static', name = '0008_initial' WHERE app = 'otp_static' AND name = '0001_initial';
UPDATE django_migrations SET app = 'authentik_stages_authenticator_static', name = '0009_throttling' WHERE app = 'otp_static' AND name = '0002_throttling';
-- Rename tables (static)
ALTER TABLE otp_static_staticdevice RENAME TO authentik_stages_authenticator_static_staticdevice;
ALTER TABLE otp_static_statictoken RENAME TO authentik_stages_authenticator_static_statictoken;
ALTER SEQUENCE otp_static_statictoken_id_seq RENAME TO authentik_stages_authenticator_static_statictoken_id_seq;
ALTER SEQUENCE otp_static_staticdevice_id_seq RENAME TO authentik_stages_authenticator_static_staticdevice_id_seq;
-- Update migrations (totp)
UPDATE django_migrations SET app = 'authentik_stages_authenticator_totp', name = '0008_initial' WHERE app = 'otp_totp' AND name = '0001_initial';
UPDATE django_migrations SET app = 'authentik_stages_authenticator_totp', name = '0009_auto_20190420_0723' WHERE app = 'otp_totp' AND name = '0002_auto_20190420_0723';
-- Rename tables (totp)
ALTER TABLE otp_totp_totpdevice RENAME TO authentik_stages_authenticator_totp_totpdevice;
ALTER SEQUENCE otp_totp_totpdevice_id_seq RENAME TO authentik_stages_authenticator_totp_totpdevice_id_seq;
COMMIT;
"""
@ -23,22 +29,9 @@ class Migration(BaseMigration):
return bool(self.cur.rowcount)
def run(self):
self.cur.execute(SQL_STATEMENT)
self.fake_migration(
(
"authentik_stages_authenticator_static",
"0008_initial",
),
(
"authentik_stages_authenticator_static",
"0009_throttling",
),
(
"authentik_stages_authenticator_totp",
"0008_initial",
),
(
"authentik_stages_authenticator_totp",
"0009_auto_20190420_0723",
),
self.cur.execute(
"SELECT * FROM django_migrations WHERE app = 'authentik_stages_authenticator_static' AND name = '0007_authenticatorstaticstage_token_length_and_more';"
)
if not bool(self.cur.rowcount):
raise Exception("Please upgrade to 2023.8 before upgrading to 2023.10")
self.cur.execute(SQL_STATEMENT)

73
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]]
name = "aiohttp"
@ -2096,16 +2096,6 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@ -2780,13 +2770,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-scim"
version = "0.0.7"
version = "0.0.8"
description = "Pydantic types for SCIM"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "pydantic-scim-0.0.7.tar.gz", hash = "sha256:bc043da51c346051dfd372f12d1837c0846b815236340156d663a8514cba5761"},
{file = "pydantic_scim-0.0.7-py3-none-any.whl", hash = "sha256:058eb195f75ef32d04eaf6369c125d5fb7052891694686f8e55e04d184ab1360"},
{file = "pydantic-scim-0.0.8.tar.gz", hash = "sha256:b6c62031126e8c54f0fc7df837678e63934a5b068533fc52e5dfb6cfc24d59e9"},
{file = "pydantic_scim-0.0.8-py3-none-any.whl", hash = "sha256:407b3bf55240947155c77a6dd839881d63368c61d64076d6b167ef124ceac79a"},
]
[package.dependencies]
@ -2840,10 +2830,7 @@ files = [
[package.dependencies]
astroid = ">=3.0.1,<=3.1.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [
{version = ">=0.3.7", markers = "python_version >= \"3.12\""},
{version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
]
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
@ -3096,7 +3083,6 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@ -3104,15 +3090,8 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@ -3129,7 +3108,6 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@ -3137,7 +3115,6 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@ -3374,28 +3351,28 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.1.2"
version = "0.1.3"
description = "An extremely fast Python linter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.1.2-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0d3ee66b825b713611f89aa35d16de984f76f26c50982a25d52cd0910dff3923"},
{file = "ruff-0.1.2-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:f85f850a320ff532b8f93e8d1da6a36ef03698c446357c8c43b46ef90bb321eb"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:809c6d4e45683696d19ca79e4c6bd3b2e9204fe9546923f2eb3b126ec314b0dc"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46005e4abb268e93cad065244e17e2ea16b6fcb55a5c473f34fbc1fd01ae34cb"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10cdb302f519664d5e2cf954562ac86c9d20ca05855e5b5c2f9d542228f45da4"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f89ebcbe57a1eab7d7b4ceb57ddf0af9ed13eae24e443a7c1dc078000bd8cc6b"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7344eaca057d4c32373c9c3a7afb7274f56040c225b6193dd495fcf69453b436"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dffa25f6e03c4950b6ac6f216bc0f98a4be9719cb0c5260c8e88d1bac36f1683"},
{file = "ruff-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42ddaea52cb7ba7c785e8593a7532866c193bc774fe570f0e4b1ccedd95b83c5"},
{file = "ruff-0.1.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8533efda625bbec0bf27da2886bd641dae0c209104f6c39abc4be5b7b22de2a"},
{file = "ruff-0.1.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b0b1b82221ba7c50e03b7a86b983157b5d3f4d8d4f16728132bdf02c6d651f77"},
{file = "ruff-0.1.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c1362eb9288f8cc95535294cb03bd4665c8cef86ec32745476a4e5c6817034c"},
{file = "ruff-0.1.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ffa7ef5ded0563329a35bd5a1cfdae40f05a75c0cc2dd30f00b1320b1fb461fc"},
{file = "ruff-0.1.2-py3-none-win32.whl", hash = "sha256:6e8073f85e47072256e2e1909f1ae515cf61ff5a4d24730a63b8b4ac24b6704a"},
{file = "ruff-0.1.2-py3-none-win_amd64.whl", hash = "sha256:b836ddff662a45385948ee0878b0a04c3a260949905ad861a37b931d6ee1c210"},
{file = "ruff-0.1.2-py3-none-win_arm64.whl", hash = "sha256:b0c42d00db5639dbd5f7f9923c63648682dd197bf5de1151b595160c96172691"},
{file = "ruff-0.1.2.tar.gz", hash = "sha256:afd4785ae060ce6edcd52436d0c197628a918d6d09e3107a892a1bad6a4c6608"},
{file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"},
{file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"},
{file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"},
{file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"},
{file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"},
{file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"},
{file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"},
{file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"},
{file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"},
{file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"},
{file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"},
]
[[package]]
@ -4331,5 +4308,5 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "e6b1df989cb5c50609540c1229d05d8458ef1cc343fb5868402db8b7679ad73c"
python-versions = "~3.11"
content-hash = "5a57dede617d149e0f307fc42580dcfd0d4b76161009dc447d6f10b048426c98"

View File

@ -1,3 +1,5 @@
# syntax=docker/dockerfile:1
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder
@ -15,7 +17,14 @@ COPY web .
RUN npm run build-proxy
# Stage 2: Build
FROM docker.io/golang:1.21.3-bookworm AS builder
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
ARG GOOS=$TARGETOS
ARG GOARCH=$TARGETARCH
WORKDIR /go/src/goauthentik.io
@ -27,9 +36,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
ENV CGO_ENABLED=0
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /go/proxy ./cmd/proxy
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
GOARM="${TARGETVARIANT#v}" go build -o /go/proxy ./cmd/proxy
# Stage 3: Run
FROM gcr.io/distroless/static-debian11:debug

View File

@ -97,7 +97,7 @@ const-rgx = "[a-zA-Z0-9_]{1,40}$"
ignored-modules = ["binascii", "socket", "zlib"]
generated-members = ["xmlsec.constants.*", "xmlsec.tree.*", "xmlsec.template.*"]
ignore = "migrations"
ignore = ["migrations", "tests"]
max-attributes = 12
max-branches = 20
@ -113,7 +113,7 @@ filterwarnings = [
[tool.poetry]
name = "authentik"
version = "2023.10.0"
version = "2023.10.5"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]
@ -151,10 +151,10 @@ packaging = "*"
paramiko = "*"
psycopg = { extras = ["c"], version = "*" }
pycryptodome = "*"
pydantic = "<3.0.0"
pydantic-scim = "^0.0.7"
pydantic = "*"
pydantic-scim = "*"
pyjwt = "*"
python = "^3.11"
python = "~3.11"
pyyaml = "*"
requests-oauthlib = "*"
sentry-sdk = "*"

View File

@ -1,5 +1,14 @@
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM docker.io/golang:1.21.3-bookworm AS builder
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
ARG GOOS=$TARGETOS
ARG GOARCH=$TARGETARCH
WORKDIR /go/src/goauthentik.io
@ -11,9 +20,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
ENV CGO_ENABLED=0
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /go/radius ./cmd/radius
RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
GOARM="${TARGETVARIANT#v}" go build -o /go/radius ./cmd/radius
# Stage 2: Run
FROM gcr.io/distroless/static-debian11:debug

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2023.10.0
version: 2023.10.5
description: Making authentication simple.
contact:
email: hello@goauthentik.io

View File

@ -36,8 +36,8 @@ class TestProviderOAuth2Github(SeleniumTestCase):
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=1 * 1_000 * 1_000_000,
),
"environment": {
"GF_AUTH_GITHUB_ENABLED": "true",

View File

@ -42,8 +42,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=1 * 1_000 * 1_000_000,
),
"environment": {
"GF_AUTH_GENERIC_OAUTH_ENABLED": "true",

View File

@ -113,8 +113,8 @@ class TestSourceOAuth2(SeleniumTestCase):
"command": "dex serve /config.yml",
"healthcheck": Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=1 * 1_000 * 1_000_000,
),
"volumes": {str(Path(CONFIG_PATH).absolute()): {"bind": "/config.yml", "mode": "ro"}},
}

View File

@ -83,8 +83,8 @@ class TestSourceSAML(SeleniumTestCase):
"auto_remove": True,
"healthcheck": Healthcheck(
test=["CMD", "curl", "http://localhost:8080"],
interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=1 * 1_000 * 1_000_000,
),
"environment": {
"SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id",

View File

@ -34,8 +34,8 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
privileged=True,
healthcheck=Healthcheck(
test=["CMD", "docker", "info"],
interval=5 * 100 * 1000000,
start_period=5 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=5 * 1_000 * 1_000_000,
),
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
volumes={

View File

@ -34,8 +34,8 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
privileged=True,
healthcheck=Healthcheck(
test=["CMD", "docker", "info"],
interval=5 * 100 * 1000000,
start_period=5 * 100 * 1000000,
interval=5 * 1_000 * 1_000_000,
start_period=5 * 1_000 * 1_000_000,
),
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
volumes={

View File

@ -27,5 +27,8 @@
"precommit": "run-s lint:precommit lint:spelling prettier",
"prettier-check": "prettier --check .",
"prettier": "prettier --write ."
},
"engines": {
"node": ">=20"
}
}

11
web/package-lock.json generated
View File

@ -17,7 +17,7 @@
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.0",
"@fortawesome/fontawesome-free": "^6.4.2",
"@goauthentik/api": "^2023.8.3-1698323628",
"@goauthentik/api": "^2023.10.1-1698348102",
"@lit-labs/context": "^0.4.1",
"@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4",
@ -100,6 +100,9 @@
"typescript": "^5.2.2",
"vite-tsconfig-paths": "^4.2.1"
},
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.19.5",
"@esbuild/linux-amd64": "^0.18.11",
@ -2883,9 +2886,9 @@
}
},
"node_modules/@goauthentik/api": {
"version": "2023.8.3-1698323628",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.8.3-1698323628.tgz",
"integrity": "sha512-qSpmrbLTCQWevjawXO26WYZcQ6Y4Cp/fIQn3KSl8fohK85MwCYLk9pqt6MZl1sC93A3dlak6sYHp9xEG3JN8wQ=="
"version": "2023.10.1-1698348102",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.1-1698348102.tgz",
"integrity": "sha512-Melx4hoHOLbgAOHREGzx83uN5BKvgql4qIUloxh/abvNeGLlfKL49caiU8++ANUaERr1vb8X2tHFwiwxtqXKeQ=="
},
"node_modules/@hcaptcha/types": {
"version": "1.0.3",

View File

@ -38,7 +38,7 @@
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.0",
"@fortawesome/fontawesome-free": "^6.4.2",
"@goauthentik/api": "^2023.8.3-1698323628",
"@goauthentik/api": "^2023.10.1-1698348102",
"@lit-labs/context": "^0.4.1",
"@lit-labs/task": "^3.1.0",
"@lit/localize": "^0.11.4",
@ -125,5 +125,8 @@
"@esbuild/darwin-arm64": "^0.19.5",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.19.5"
},
"engines": {
"node": ">=20"
}
}

View File

@ -116,7 +116,7 @@ export class ApplicationForm extends ModelForm<Application, string> {
return app;
}
handleConfirmBackchannelProviders({ items }: { items: Provider[] }) {
handleConfirmBackchannelProviders(items: Provider[]) {
this.backchannelProviders = items;
this.requestUpdate();
return Promise.resolve();

View File

@ -63,7 +63,7 @@ export class AkBackchannelProvidersInput extends AKElement {
return html`
<ak-form-element-horizontal label=${this.label} name=${name}>
<div class="pf-c-input-group">
<ak-provider-select-table ?backchannelOnly=${true} .confirm=${confirm}>
<ak-provider-select-table ?backchannelOnly=${true} .confirm=${this.confirm}>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
${this.tooltip ? this.tooltip : nothing}
<i class="fas fa-plus" aria-hidden="true"></i>

View File

@ -114,8 +114,8 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
label=${msg("Client type")}
.value=${provider?.clientType}
required
@change=${(ev: CustomEvent<ClientTypeEnum>) => {
this.showClientSecret = ev.detail !== ClientTypeEnum.Public;
@change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
}}
.options=${clientTypeOptions}
>

View File

@ -78,8 +78,8 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Mode")} ?required=${true} name="mode">
<ak-radio
@change=${(ev: CustomEvent<NotificationTransportModeEnum>) => {
this.onModeChange(ev.detail);
@change=${(ev: CustomEvent<{ value: NotificationTransportModeEnum }>) => {
this.onModeChange(ev.detail.value);
}}
.options=${[
{

View File

@ -210,8 +210,8 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
label=${msg("Client type")}
.value=${provider?.clientType}
required
@change=${(ev: CustomEvent<ClientTypeEnum>) => {
this.showClientSecret = ev.detail !== ClientTypeEnum.Public;
@change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => {
this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public;
}}
.options=${clientTypeOptions}
>
@ -334,13 +334,14 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
)}
>
</ak-radio-input>
<ak-switch-input name="includeClaimsInIdToken">
<ak-switch-input
name="includeClaimsInIdToken"
label=${msg("Include claims in id_token")}
?checked=${first(provider?.includeClaimsInIdToken, true)}
help=${msg(
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
)}></ak-switch-input
>
)}
></ak-switch-input>
<ak-radio-input
name="issuerMode"
label=${msg("Issuer mode")}

View File

@ -42,15 +42,13 @@ export class RoleForm extends ModelForm<Role, string> {
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
return html`<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>`;
}
}

View File

@ -184,28 +184,31 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
</p>
</ak-form-element-horizontal> `
: html``}
${this.providerType.slug === ProviderTypeEnum.Openidconnect
? html`
<ak-form-element-horizontal
label=${msg("OIDC Well-known URL")}
name="oidcWellKnownUrl"
>
<input
type="text"
value="${first(
this.instance?.oidcWellKnownUrl,
this.providerType.oidcWellKnownUrl,
"",
)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
"OIDC well-known configuration URL. Can be used to automatically configure the URLs above.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
${this.providerType.slug === ProviderTypeEnum.Openidconnect ||
this.providerType.oidcWellKnownUrl !== ""
? html`<ak-form-element-horizontal
label=${msg("OIDC Well-known URL")}
name="oidcWellKnownUrl"
>
<input
type="text"
value="${first(
this.instance?.oidcWellKnownUrl,
this.providerType.oidcWellKnownUrl,
"",
)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
"OIDC well-known configuration URL. Can be used to automatically configure the URLs above.",
)}
</p>
</ak-form-element-horizontal>`
: html``}
${this.providerType.slug === ProviderTypeEnum.Openidconnect ||
this.providerType.oidcJwksUrl !== ""
? html`<ak-form-element-horizontal
label=${msg("OIDC JWKS URL")}
name="oidcJwksUrl"
>
@ -224,7 +227,6 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("OIDC JWKS")} name="oidcJwks">
<ak-codemirror
mode=${CodeMirrorMode.JavaScript}
@ -232,8 +234,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${msg("Raw JWKS data.")}</p>
</ak-form-element-horizontal>
`
</ak-form-element-horizontal>`
: html``}
</div>
</ak-form-group>`;
@ -386,6 +387,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Consumer secret")}
@ -394,6 +396,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
name="consumerSecret"
>
<textarea class="pf-c-form-control"></textarea>
<p class="pf-c-form__helper-text">${msg("Also known as Client Secret.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes">
<input

View File

@ -15,6 +15,8 @@ export class UserDeviceTable extends Table<Device> {
@property({ type: Number })
userId?: number;
checkbox = true;
async apiEndpoint(): Promise<PaginatedResponse<Device>> {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsAdminAllList({
@ -64,6 +66,21 @@ export class UserDeviceTable extends Table<Device> {
}
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Device(s)")}
.objects=${this.selectedElements}
.delete=${(item: Device) => {
return this.deleteWrapper(item);
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
renderToolbar(): TemplateResult {
return html` <ak-spinner-button
.callAction=${() => {

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2023.10.0";
export const VERSION = "2023.10.5";
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";

View File

@ -310,6 +310,12 @@ select[multiple] option:checked {
--pf-c-wizard__nav-link--before--BackgroundColor: transparent;
}
/* tree view */
.pf-c-tree-view__node {
--pf-c-tree-view__node--Color: var(--ak-dark-foreground);
}
.pf-c-tree-view__node-toggle {
--pf-c-tree-view__node-toggle--Color: var(--ak-dark-foreground);
}
.pf-c-tree-view__node:focus {
--pf-c-tree-view__node--focus--BackgroundColor: var(--ak-dark-background-light-ish);
}

View File

@ -79,6 +79,7 @@ export class PageHeader extends AKElement {
}
.pf-c-page__main-section {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
justify-content: center;

View File

@ -80,11 +80,12 @@ export class IdentificationStage extends BaseStage<
}
createHelperForm(): void {
const compatMode = "ShadyDOM" in window;
this.form = document.createElement("form");
document.documentElement.appendChild(this.form);
// Only add the additional username input if we're in a shadow dom
// otherwise it just confuses browsers
if (!("ShadyDOM" in window)) {
if (!compatMode) {
// This is a workaround for the fact that we're in a shadow dom
// adapted from https://github.com/home-assistant/frontend/issues/3133
const username = document.createElement("input");
@ -104,30 +105,33 @@ export class IdentificationStage extends BaseStage<
};
this.form.appendChild(username);
}
const password = document.createElement("input");
password.setAttribute("type", "password");
password.setAttribute("name", "password");
password.setAttribute("autocomplete", "current-password");
password.onkeyup = (ev: KeyboardEvent) => {
if (ev.key == "Enter") {
this.submitForm(ev);
}
const el = ev.target as HTMLInputElement;
// Because the password field is not actually on this page,
// and we want to 'prefill' the password for the user,
// save it globally
PasswordManagerPrefill.password = el.value;
// Because password managers fill username, then password,
// we need to re-focus the uid_field here too
(this.shadowRoot || this)
.querySelectorAll<HTMLInputElement>("input[name=uidField]")
.forEach((input) => {
// Because we assume only one input field exists that matches this
// call focus so the user can press enter
input.focus();
});
};
this.form.appendChild(password);
// Only add the password field when we don't already show a password field
if (!compatMode && !this.challenge.passwordFields) {
const password = document.createElement("input");
password.setAttribute("type", "password");
password.setAttribute("name", "password");
password.setAttribute("autocomplete", "current-password");
password.onkeyup = (ev: KeyboardEvent) => {
if (ev.key == "Enter") {
this.submitForm(ev);
}
const el = ev.target as HTMLInputElement;
// Because the password field is not actually on this page,
// and we want to 'prefill' the password for the user,
// save it globally
PasswordManagerPrefill.password = el.value;
// Because password managers fill username, then password,
// we need to re-focus the uid_field here too
(this.shadowRoot || this)
.querySelectorAll<HTMLInputElement>("input[name=uidField]")
.forEach((input) => {
// Because we assume only one input field exists that matches this
// call focus so the user can press enter
input.focus();
});
};
this.form.appendChild(password);
}
const totp = document.createElement("input");
totp.setAttribute("type", "text");
totp.setAttribute("name", "code");

View File

@ -96,7 +96,7 @@ export class LibraryApplication extends AKElement {
this.application.metaPublisher !== "" ||
this.application.metaDescription !== "";
const classes = { "pf-m-selectable pf-m-selected": this.selected };
const classes = { "pf-m-selectable": this.selected, "pf-m-selected": this.selected };
const styles = this.background ? { background: this.background } : {};
return html` <div

View File

@ -38,7 +38,9 @@ export class LibraryPageApplicationList extends AKElement {
];
@property({ attribute: false })
apps: Application[] = [];
set apps(value: Application[]) {
this.fuse.setCollection(value);
}
@property()
query = getURLParam<string | undefined>("search", undefined);
@ -63,7 +65,7 @@ export class LibraryPageApplicationList extends AKElement {
shouldSort: true,
ignoreFieldNorm: true,
useExtendedSearch: true,
threshold: 0.5,
threshold: 0.3,
});
}
@ -77,7 +79,6 @@ export class LibraryPageApplicationList extends AKElement {
connectedCallback() {
super.connectedCallback();
this.fuse.setCollection(this.apps);
if (!this.query) {
return;
}

View File

@ -82,9 +82,9 @@ export class UserInterface extends Interface {
:host([theme="dark"]) .pf-c-page__header {
color: var(--ak-dark-foreground) !important;
}
.pf-c-page__header-tools-item .fas,
.pf-c-notification-badge__count,
.pf-c-page__header-tools-group .pf-c-button {
:host([theme="light"]) .pf-c-page__header-tools-item .fas,
:host([theme="light"]) .pf-c-notification-badge__count,
:host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button {
color: var(--ak-global--Color--100) !important;
}
.pf-c-page {
@ -183,7 +183,7 @@ export class UserInterface extends Interface {
<ak-enterprise-status interface="user"></ak-enterprise-status>
<div class="pf-c-page">
<div class="background-wrapper" style="${this.uiConfig.theme.background}">
${this.uiConfig.theme.background === ""
${(this.uiConfig.theme.background || "") === ""
? html`<div class="background-default-slant"></div>`
: html``}
</div>

View File

@ -5798,12 +5798,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
@ -6031,6 +6025,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source>
</trans-unit>
<trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source>
</trans-unit>
<trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source>
</trans-unit>
<trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source>
</trans-unit>
<trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source>
</trans-unit>
</body>
</file>

View File

@ -6079,12 +6079,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
@ -6312,6 +6306,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source>
</trans-unit>
<trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source>
</trans-unit>
<trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source>
</trans-unit>
<trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source>
</trans-unit>
<trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source>
</trans-unit>
</body>
</file>

View File

@ -5713,12 +5713,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
@ -5946,6 +5940,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source>
</trans-unit>
<trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source>
</trans-unit>
<trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source>
</trans-unit>
<trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source>
</trans-unit>
<trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source>
</trans-unit>
</body>
</file>

File diff suppressed because it is too large Load Diff

View File

@ -5921,12 +5921,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
@ -6154,6 +6148,24 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source>
</trans-unit>
<trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source>
</trans-unit>
<trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source>
</trans-unit>
<trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source>
</trans-unit>
<trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source>
</trans-unit>
</body>
</file>

View File

@ -7557,14 +7557,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>For transparent reverse proxies with required authentication</source>
<target>Ƒōŕ ţŕàńśƥàŕēńţ ŕēvēŕśē ƥŕōxĩēś ŵĩţĥ ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń</target>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefik's forwardAuth</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ</target>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ ƥēŕ ŕōōţ ďōmàĩń</target>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
<target>Ćōńƒĩĝũŕē ŚÀMĹ ƥŕōvĩďēŕ màńũàĺĺŷ</target>
@ -7844,4 +7836,22 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s0924f51b028233a3">
<source>&lt;No name set&gt;</source>
</trans-unit>
<trans-unit id="sdc9a6ad1af30572c">
<source>For nginx's auth_request or traefik's forwardAuth</source>
</trans-unit>
<trans-unit id="sfc31264ef7ff86ef">
<source>For nginx's auth_request or traefik's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="sc615309d10a9228c">
<source>RBAC is in preview.</source>
</trans-unit>
<trans-unit id="s32babfed740fd3c1">
<source>User type used for newly created users.</source>
</trans-unit>
<trans-unit id="sb35c08e3a541188f">
<source>Also known as Client ID.</source>
</trans-unit>
<trans-unit id="sd46fd9b647cfea10">
<source>Also known as Client Secret.</source>
</trans-unit>
</body></file></xliff>

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