Compare commits

...

200 Commits

Author SHA1 Message Date
faaed59b44 Extraneous whitespace. Darnit. 2024-11-01 14:56:00 -07:00
3eb55c6766 Still trying to get the docs to line up. 2024-11-01 14:55:32 -07:00
cadac325f5 And, having figured that out, make the documentation match. 2024-11-01 14:54:36 -07:00
91aa33875a And, having figured that out, make the documentation match. 2024-11-01 14:53:29 -07:00
6f2996a43e Make the comment correspond to the code. A good catch by lit-analyzer. 2024-11-01 14:50:38 -07:00
dca9d84ff3 Do not camelize 'topmost'. 2024-11-01 14:45:01 -07:00
d97d149685 Removing duplicate TagNameMap declarations. 2024-11-01 14:42:31 -07:00
26ae6e2da6 This is out-of-date, but I don't want to lose the documentation work. 2024-11-01 14:36:39 -07:00
e2a9957308 Merge artifacts. These are no longer necessary. 2024-11-01 14:30:02 -07:00
d8c0793f5d Nesting slots is tricky, and the purpose of our Story objects is to illustrate how they work. In this case, the container object doesn't have a named slot, so providing a name confused the renderer and it didn't render the message as expected. This is now fixed. 2024-11-01 14:28:27 -07:00
3f691b8a76 Duplicate test, unneeded. 2024-11-01 13:58:08 -07:00
fcc7c39ee2 Merge branch 'main' into web/cleanup/20240516-simple-element-docs-1
* main: (159 commits)
  website: bump elliptic from 6.5.7 to 6.6.0 in /website (#11869)
  core: bump selenium from 4.25.0 to 4.26.0 (#11875)
  core: bump goauthentik.io/api/v3 from 3.2024083.14 to 3.2024100.1 (#11876)
  website/docs: add info about invalidation flow, default flows in general (#11800)
  website: fix docs redirect (#11873)
  website: remove RC disclaimer for version 2024.10 (#11871)
  website: update supported versions (#11841)
  web: bump API Client version (#11870)
  root: backport version bump 2024.10.0 (#11868)
  website/docs: 2024.8.4 release notes (#11862)
  web/admin: provide default invalidation flows for LDAP and Radius (#11861)
  core, web: update translations (#11858)
  web/admin: fix code-based MFA toggle not working in wizard (#11854)
  sources/kerberos: add kiprop to ignored system principals (#11852)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11846)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#11845)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#11847)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#11848)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11849)
  translate: Updates for file web/xliff/en.xlf in it (#11850)
  ...
2024-11-01 13:23:33 -07:00
f192690f25 website: bump elliptic from 6.5.7 to 6.6.0 in /website (#11869)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-31 14:33:00 +01:00
ecd013401c core: bump selenium from 4.25.0 to 4.26.0 (#11875)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.25.0 to 4.26.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.25.0...selenium-4.26.0)

---
updated-dependencies:
- dependency-name: selenium
  dependency-type: direct:development
  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>
2024-10-31 14:31:45 +01:00
38adb41244 core: bump goauthentik.io/api/v3 from 3.2024083.14 to 3.2024100.1 (#11876)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.14 to 3.2024100.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.14...v3.2024100.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>
2024-10-31 14:31:37 +01:00
712e5084c1 website/docs: add info about invalidation flow, default flows in general (#11800)
* restructure

* tweak

* fix header

* added more definitions

* jens excellent idea

* restructure the Layouts content

* tweaks

* links fix

* links still

* fighting links and cache

* argh links

* ditto

* remove link

* anothe link

* Jens' edit

* listed default flows set by brand

* add links back

* tweaks

* used import for list

* tweak

* rewrite some stuff

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

* format

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

* mangled rebase, fixed

* bump

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tana@goauthentik.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-10-31 08:26:51 -05:00
e2f8574b6a website: fix docs redirect (#11873)
fix docs redirect
2024-10-31 00:48:21 +00:00
d43940d5d6 website: remove RC disclaimer for version 2024.10 (#11871) 2024-10-31 01:31:41 +01:00
faaba483a0 website: update supported versions (#11841)
update supported versions
2024-10-31 01:02:37 +01:00
fa78c24516 web: bump API Client version (#11870)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-30 23:59:56 +00:00
8245d08ddb root: backport version bump 2024.10.0 (#11868)
* release: 2024.10.0-rc1

* root: `bumpversion` 2024.10 (#11865)

release: 2024.10.0
2024-10-31 00:39:41 +01:00
f452617f29 website/docs: 2024.8.4 release notes (#11862)
* website/docs: 2024.8.4 release notes

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

* typo

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-30 20:05:02 +01:00
ed6d1880a0 web/admin: provide default invalidation flows for LDAP and Radius (#11861)
* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

* web/admin: provide default invalidation flows for LDAP provider.

* admin/web: the default invalidation flows for LDAP and Radius are different from the others.
2024-10-30 17:36:02 +00:00
2909a15009 core, web: update translations (#11858)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-30 15:27:59 +01:00
8601638831 web/admin: fix code-based MFA toggle not working in wizard (#11854)
closes #11834

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-29 20:18:49 +01:00
c38adcf25a sources/kerberos: add kiprop to ignored system principals (#11852) 2024-10-29 17:30:33 +01:00
ce92f77372 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11846)
Translate locale/en/LC_MESSAGES/django.po in zh_CN

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-29 16:01:08 +01:00
ceb702b19e translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#11845)
Translate locale/en/LC_MESSAGES/django.po in it

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-29 16:00:36 +01:00
c445fbf544 translate: Updates for file web/xliff/en.xlf in zh_CN (#11847)
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>
2024-10-29 16:00:17 +01:00
087fa4306f translate: Updates for file web/xliff/en.xlf in zh-Hans (#11848)
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>
2024-10-29 16:00:03 +01:00
88b90a365c translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11849)
Translate django.po in zh-Hans

100% translated source file: 'django.po'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-29 15:59:48 +01:00
626a5397ca translate: Updates for file web/xliff/en.xlf in it (#11850)
Translate web/xliff/en.xlf in it

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-29 15:59:34 +01:00
cace69d6f8 website: 2024.10 Release Notes (#11839)
* generate diffs and changelog

* add 2024.10 release notes

* reorder release note highlights

* lint website

* reorder release note new features

* reword Kerberos

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>

* extend JWE description

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>

---------

Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-10-28 16:13:31 +00:00
5d1e7a847a translate: Updates for file web/xliff/en.xlf in zh-Hans (#11814)
* Translate web/xliff/en.xlf in zh-Hans

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

* Removing web/xliff/en.xlf in zh-Hans

99% of minimum 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>
2024-10-28 15:28:33 +01:00
d27d222ab3 core, web: update translations (#11821)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-28 13:23:12 +01:00
b16e1e7f96 core: bump goauthentik.io/api/v3 from 3.2024083.13 to 3.2024083.14 (#11830)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.13 to 3.2024083.14.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.13...v3.2024083.14)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-28 13:23:01 +01:00
c0128945e3 core: bump service-identity from 24.1.0 to 24.2.0 (#11831)
Bumps [service-identity](https://github.com/sponsors/hynek) from 24.1.0 to 24.2.0.
- [Commits](https://github.com/sponsors/hynek/commits)

---
updated-dependencies:
- dependency-name: service-identity
  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>
2024-10-28 13:22:50 +01:00
5447c2e78e core: bump twilio from 9.3.5 to 9.3.6 (#11832)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.3.5 to 9.3.6.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.3.5...9.3.6)

---
updated-dependencies:
- dependency-name: twilio
  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>
2024-10-28 13:22:33 +01:00
15f173d8d4 core: bump pytest-randomly from 3.15.0 to 3.16.0 (#11833)
Bumps [pytest-randomly](https://github.com/pytest-dev/pytest-randomly) from 3.15.0 to 3.16.0.
- [Changelog](https://github.com/pytest-dev/pytest-randomly/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-randomly/compare/3.15.0...3.16.0)

---
updated-dependencies:
- dependency-name: pytest-randomly
  dependency-type: direct:development
  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>
2024-10-28 13:21:27 +01:00
b533f416b8 website/docs: Update social-logins github (#11822)
Update index.md

Signed-off-by: Tobias <5702338+T0biii@users.noreply.github.com>
2024-10-28 13:04:54 +01:00
57dc595cfb website/docs: remove � (#11823)
remove 

Signed-off-by: Tobias <5702338+T0biii@users.noreply.github.com>
2024-10-28 13:04:38 +01:00
88a90e241a lifecycle: fix kdc5-config missing (#11826)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-27 21:26:55 +01:00
eac3e88126 website/docs: update preview status of different features (#11817)
* remove preview from RAC

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

* add preview page instead of info box

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

* remove preview from rbac

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

* add preview to gdtc

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

* add preview to kerberos source

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-25 21:37:20 +02:00
abfc907ad6 lifecycle: fix missing krb5 deps for full testing in image (#11815)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-25 18:42:54 +02:00
31014ba1e5 translate: Updates for file web/xliff/en.xlf in zh-Hans (#11810)
* Translate web/xliff/en.xlf in zh-Hans

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

* Removing web/xliff/en.xlf in zh-Hans

99% of minimum 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>
2024-10-25 14:54:51 +02:00
5c76145d10 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11809)
Translate django.po in zh-Hans

100% translated source file: 'django.po'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-25 14:39:58 +02:00
cdfe4ccf71 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11808)
Translate locale/en/LC_MESSAGES/django.po in zh_CN

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-25 14:39:42 +02:00
bd21431c53 web: bump API Client version (#11807)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-25 14:39:23 +02:00
1c4d4ff5f2 core: bump goauthentik.io/api/v3 from 3.2024083.12 to 3.2024083.13 (#11806)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.12 to 3.2024083.13.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.12...v3.2024083.13)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-25 14:39:01 +02:00
5efeae0f39 core: bump ruff from 0.7.0 to 0.7.1 (#11805)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.0 to 0.7.1.
- [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/0.7.0...0.7.1)

---
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>
2024-10-25 14:38:54 +02:00
4253d7e115 core: bump twilio from 9.3.4 to 9.3.5 (#11804)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.3.4 to 9.3.5.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.3.4...9.3.5)

---
updated-dependencies:
- dependency-name: twilio
  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>
2024-10-25 14:38:41 +02:00
0a9d88e49a core, web: update translations (#11803)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-25 14:38:26 +02:00
97e7736448 providers/scim: handle no members in group in consistency check (#11801)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-25 12:48:52 +02:00
9ee0ba141c stages/identification: add captcha to identification stage (#11711)
* add captcha to identification stage

* simplify component invocations

* fail fast on `onTokenChange` default behavior

* reword docs

* rename `token` to `captcha_token` in Identification stage contexts

(In Captcha stage contexts the name `token` seems well-scoped.)

* use `nothing` instead of ``` html`` ```

* remove rendered Captcha component from document flow on Identification stages

Note: this doesn't remove the captcha itself, if interactive, only the loading
indicator.

* add invisible requirement to captcha on Identification stage

* stylize docs

* add friendlier error messages to Captcha stage

* fix tests

* make captcha error messages even friendlier

* add test case to retriable captcha

* use default

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-10-25 08:13:35 +02:00
b7cccf5ad2 website/docs: improve root page and redirect (#11798)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-25 00:42:59 +02:00
3b6d93dc2a providers/scim: clamp batch size for patch requests (#11797)
* providers/scim: clamp batch size for patch requests

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

* sanity check for empty patch request instead

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-24 22:01:10 +02:00
3fc0904425 web/admin: fix missing div in wizard forms (#11794)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-24 17:56:20 +02:00
f482937474 providers/proxy: fix handling of AUTHENTIK_HOST_BROWSER (#11722)
* providers/proxy: fix handling of AUTHENTIK_HOST_BROWSER (#9622/#4688/#6476)

* chore: fix tests
2024-10-24 16:34:45 +02:00
238a396309 core, web: update translations (#11789)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-24 13:05:33 +02:00
0a18c67b7e core: bump goauthentik.io/api/v3 from 3.2024083.11 to 3.2024083.12 (#11790)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.11 to 3.2024083.12.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.11...v3.2024083.12)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-24 13:04:28 +02:00
a541e4fc9d core: bump gssapi from 1.8.3 to 1.9.0 (#11791)
Bumps [gssapi](https://github.com/pythongssapi/python-gssapi) from 1.8.3 to 1.9.0.
- [Release notes](https://github.com/pythongssapi/python-gssapi/releases)
- [Commits](https://github.com/pythongssapi/python-gssapi/compare/v1.8.3...v1.9.0)

---
updated-dependencies:
- dependency-name: gssapi
  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>
2024-10-24 13:04:07 +02:00
b6bdcd6c05 web: bump API Client version (#11792)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-24 13:03:50 +02:00
70075e6f0a stages/authenticator_validate: autoselect last used 2fa device (#11087)
* authenticator_validate: autoselect last used device class

* improve usability of `AuthenticatorValidationStage`

* don't automatically offer the recovery key authenticator validation

I believe this could confuse users more than help them

* web: move mutator block into the `willUpdate` override

Removed the section of code from the renderer that updates the state of the component;
Mutating in the middle of a render is strongly discouraged.  This block contains an
algorithm for determining if the selectedDeviceChallenge should be set and how; since
`selectedDeviceChallenge` is a state, we don't want to be changing it outside of those
lifecycle methods that do not trigger a rerender.

* web: move styles() to top of class, extract custom CSS to a named block.

* lint: collapse multiple early returns, missing curly brace.

* autoselect device only once even if the user only has 1 device

* make `DeviceChallenge.last_used` nullable instead of optional

* clarify button text

* fix typo

* add docs for automatic device selection

* update docs

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>

* fix punctuation

---------

Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Ken Sternberg <ken@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-10-24 09:04:40 +02:00
dc670da27f translate: Updates for file web/xliff/en.xlf in fr (#11785)
Translate web/xliff/en.xlf in fr

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-24 01:20:21 +02:00
76390dc47b translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#11784)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-24 01:19:54 +02:00
9b64db7076 web/admin: Add InvalidationFlow to Radius Provider dialogues (#11786)
web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.
2024-10-23 21:38:56 +00:00
4c942389ce core, web: update translations (#11782)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-23 21:34:23 +02:00
3bdb287b78 providers/oauth2: fix amr claim not set due to login event not associated (#11780)
* providers/oauth2: fix amr claim not set due to login event not associated

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

* add sid claim

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

* import engine only once

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

* remove manual sid extraction from proxy, add test, make session key hashing more obvious

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

* unrelated string fix

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

* fix format

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

* fix tests

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 21:29:18 +02:00
da73d4f784 web/admin: add strict dompurify config for diagram (#11783)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 19:42:54 +02:00
40c7fefd96 core: bump cryptography from 43.0.1 to 43.0.3 (#11750)
Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.1 to 43.0.3.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/43.0.1...43.0.3)

---
updated-dependencies:
- dependency-name: cryptography
  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>
2024-10-23 18:28:56 +02:00
7fe7cfee22 web: bump API Client version (#11781)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-23 18:01:01 +02:00
d817c646bd sources: add Kerberos (#10815)
* sources: introduce new property mappings per-user and group

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

* sources/ldap: migrate to new property mappings

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

* lint-fix and make gen

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

* web changes

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

* fix tests

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

* update tests

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

* remove flatten for generic implem

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

* rework migration

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

* lint-fix

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

* wip

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

* fix migrations

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

* re-add field migration to property mappings

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

* fix migrations

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

* more migrations fixes

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

* easy fixes

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

* migrate to propertymappingmanager

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

* ruff and small fixes

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

* move mapping things into a separate class

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

* migrations: use using(db_alias)

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

* migrations: use built-in variable

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

* add docs

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

* add release notes

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

* wip

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

* wip

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

* wip

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

* wip

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

* wip

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

* wip

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

* wip

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

* wip

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

* lint

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

* fix login reverse

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

* wip

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

* wip

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

* refactor source flow manager matching

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

* kerberos sync with mode matching

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

* fixup

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

* wip

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

* finish frontend

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

* Optimised images with calibre/image-actions

* make web

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

* add test for internal password update

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

* fix sync tests

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

* fix filter

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

* switch to blueprints property mappings, improvements to frontend

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

* some more small fixes

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

* fix reverse

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

* properly deal with password changes signals

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

* actually deal with it properly

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

* fix

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

* update docs

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

* fix

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

* fix

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

* lint-fix

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

* blueprints: realm as group: make it non default

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

* small fixes and improvements

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

* wip

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

* fix title

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

* add password backend to default flow

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

* link docs page properly, add in admin interface, add suggestions for how to apply changes to a fleet of machines

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

* add troubleshooting

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

* fix default flow pass backend

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

* fix flaky spnego tests

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

* lint

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

* properly convert gssapi name to python str

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

* fix unpickable types

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

* make sure the last server token is returned to the client

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

* lint

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

* Update website/docs/developer-docs/setup/full-dev-environment.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

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

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

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

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

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

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/browser.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

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

* Update website/docs/users-sources/sources/protocols/kerberos/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* more docs review

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

* fix missing library

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

* fix missing library again

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

* fix web import

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

* fix sync

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

* fix sync v2

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

* fix sync v3

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-10-23 17:58:29 +02:00
d3ebfcaf2f root: rework CSRF middleware to set secure flag (#11753)
root: remove custom CSRF middleware

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 13:59:02 +02:00
3c0a8f4641 web/admin: improve invalidation flow default & field grouping (#11769)
* web/admin: auto-select provider invalidation flow

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

* new structuring

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

* fix missing ldap unbind flow

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

* unrelated: add enter for redirect

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 13:58:44 +02:00
d3d96b7bed providers/scim: add comparison with existing group on update and delta update users (#11414)
* fix incorrect default group mapping

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

* providers/scim: add comparison with existing group on update and delta update users

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

* fix

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

* fix

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

* fix another exception when creating groups

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

* fix users to add check

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 12:39:17 +02:00
a2877364c8 website: bump mermaid from 10.6.0 to 10.9.3 in /website (#11766)
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 10.6.0 to 10.9.3.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Changelog](https://github.com/mermaid-js/mermaid/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/mermaid-js/mermaid/compare/v10.6.0...v10.9.3)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 11:15:42 +02:00
a5a26a50c6 web/flows: use dompurify for footer links (#11773)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 11:15:23 +02:00
12dbdfaf66 core, web: update translations (#11775)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-23 11:15:15 +02:00
1116b89c08 core: bump goauthentik.io/api/v3 from 3.2024083.10 to 3.2024083.11 (#11776)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.10 to 3.2024083.11.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.10...v3.2024083.11)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-23 11:14:54 +02:00
5eb84aef1e website: bump @types/react from 18.3.11 to 18.3.12 in /website (#11777)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.3.11 to 18.3.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  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>
2024-10-23 11:14:44 +02:00
444a0682ab website: bump http-proxy-middleware from 2.0.6 to 2.0.7 in /website (#11771)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 22:59:19 +02:00
f6a6124050 web: bump API Client version (#11770)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-22 22:56:05 +02:00
cec3fdb612 stages: authenticator_endpoint_gdtc (#10477)
* rework

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

* add loading overlay for chrome

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

* start docs

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

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens L. <jens@beryju.org>

* save data

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

* fix web ui, prevent deletion

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

* fix

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

* text fixes

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-10-22 22:46:46 +02:00
0e4e7ccb4b core: add prompt_data to auth flow (#11702)
I added the prompt_data and user_path to the auth flow. This allows us to more easily sync users details whenever they're logged in through a Source by using the Write stage, identical to an  Enrolment flow.

This makes sure that mappings etc are automatically taken into consideration, and are passed to the Authentication flow.

While I was at it, I made the code consistent with the `handle_enroll` method.

Signed-off-by: Wouter van Os <wouter0100@gmail.com>
2024-10-22 18:14:14 +02:00
2fa50de470 tests/e2e: fix dex tests failing (#11761)
* tests/e2e: fix dex tests failing

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

* force no special chars

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-22 01:52:16 +02:00
af4a1e4576 web/rac: disable DPI scaling (#11757)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-21 17:28:57 +02:00
b6da6219fb web/admin: update flow background (#11758)
* web/admin: update flow background

https://unsplash.com/photos/gray-concrete-road-between-trees-near-mountain-z8ct_Q3oCqM
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Optimised images with calibre/image-actions

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-21 17:24:51 +02:00
1932993b2c website/docs: fix some broken links (#11742)
* Update security-hardening.md broken links

Signed-off-by: Norbert Takács <bokker11@hotmail.com>

* Removed extra link

Signed-off-by: Norbert Takács <bokker11@hotmail.com>

* added space back

Signed-off-by: Norbert Takács <bokker11@hotmail.com>

* fix netlify redirects

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

* use relative links

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

---------

Signed-off-by: Norbert Takács <bokker11@hotmail.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-10-21 09:54:14 -05:00
277895ead2 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#11755)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-21 14:27:04 +00:00
adfa1b16f3 core, web: update translations (#11756)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-21 16:17:25 +02:00
d3cf27f8f0 translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#11751)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-21 15:12:13 +02:00
d5cf76efe1 translate: Updates for file web/xliff/en.xlf in fr (#11752)
Translate web/xliff/en.xlf in fr

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-21 15:11:55 +02:00
24abe92fa3 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11735)
Translate django.po in zh-Hans

100% translated source file: 'django.po'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-21 13:22:28 +02:00
e3d458d3b0 translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#11737)
Translate locale/en/LC_MESSAGES/django.po in it

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'it'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-21 13:22:15 +02:00
bb809cd86d translate: Updates for file web/xliff/en.xlf in zh_CN (#11733)
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>
2024-10-21 13:22:00 +02:00
aa5c5b5c67 translate: Updates for file web/xliff/en.xlf in zh-Hans (#11734)
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>
2024-10-21 13:21:45 +02:00
0bcebdff1f translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11732)
Translate locale/en/LC_MESSAGES/django.po in zh_CN

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-21 13:21:29 +02:00
78b554b327 website: bump @mdx-js/react from 3.0.1 to 3.1.0 in /website (#11748)
Bumps [@mdx-js/react](https://github.com/mdx-js/mdx/tree/HEAD/packages/react) from 3.0.1 to 3.1.0.
- [Release notes](https://github.com/mdx-js/mdx/releases)
- [Changelog](https://github.com/mdx-js/mdx/blob/main/changelog.md)
- [Commits](https://github.com/mdx-js/mdx/commits/3.1.0/packages/react)

---
updated-dependencies:
- dependency-name: "@mdx-js/react"
  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>
2024-10-21 11:34:55 +02:00
9736b7a391 core: bump coverage from 7.6.3 to 7.6.4 (#11749)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.3 to 7.6.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.3...7.6.4)

---
updated-dependencies:
- dependency-name: coverage
  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>
2024-10-21 11:34:37 +02:00
352223f35e web/admin: fix sync single button throwing error (#11727)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-18 19:12:37 +02:00
6c6c9a044b web/admin: fix invalid create date shown for MFA registered before date was saved (#11728)
web/admin: fix invalid create date shown for MFA registered before date was tracked

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-18 18:42:27 +02:00
4c9820751f stages/authenticator: use RBAC for devices API (#11482)
* stages/authenticator: use RBAC for devices API

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Update authentik/core/api/devices.py

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* add tests

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

* make lint

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

* Update authentik/core/tests/test_devices_api.py

Co-authored-by: Jens L. <jens@goauthentik.io>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Jens L. <jens@goauthentik.io>
2024-10-18 18:06:29 +02:00
f0e8ae8536 policies/event_matcher: fix inconsistent behaviour (#11724)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-18 16:31:59 +02:00
24d69ff5ed website/integrations: Add note regarding custom scopes in Hashicorp Vault OIDC documentation (#11668)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-10-18 13:16:08 +00:00
0751b91893 providers/oauth2: don't overwrite attributes when updating service acccount (#11709)
providers/oauth2: don't overwrite attributes when updating service account

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-18 13:36:05 +02:00
ce1e7bef26 core: bump goauthentik.io/api/v3 from 3.2024083.8 to 3.2024083.10 (#11721)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.8 to 3.2024083.10.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Changelog](https://github.com/goauthentik/client-go/blob/main/model_version_history.go)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.8...v3.2024083.10)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-18 13:35:51 +02:00
0b0dd310bd core, web: update translations (#11715)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-18 13:35:42 +02:00
52b6621128 core: bump msgraph-sdk from 1.10.0 to 1.11.0 (#11716)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/microsoftgraph/msgraph-sdk-python/releases)
- [Changelog](https://github.com/microsoftgraph/msgraph-sdk-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/microsoftgraph/msgraph-sdk-python/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: msgraph-sdk
  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>
2024-10-18 13:35:34 +02:00
28cb0521bb core: bump twilio from 9.3.3 to 9.3.4 (#11717)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.3.3 to 9.3.4.
- [Release notes](https://github.com/twilio/twilio-python/releases)
- [Changelog](https://github.com/twilio/twilio-python/blob/main/CHANGES.md)
- [Commits](https://github.com/twilio/twilio-python/compare/9.3.3...9.3.4)

---
updated-dependencies:
- dependency-name: twilio
  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>
2024-10-18 13:35:25 +02:00
581492c2c5 core: bump sentry-sdk from 2.16.0 to 2.17.0 (#11718)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.16.0...2.17.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  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>
2024-10-18 13:35:18 +02:00
849c6dbee6 core: bump ruff from 0.6.9 to 0.7.0 (#11719)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.9 to 0.7.0.
- [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/0.6.9...0.7.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  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>
2024-10-18 13:35:06 +02:00
634b559b13 core: bump github.com/redis/go-redis/v9 from 9.6.2 to 9.7.0 (#11720)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.6.2 to 9.7.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.6.2...v9.7.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  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>
2024-10-18 13:34:52 +02:00
b57df12ace core: extract object matching from flow manager (#11458) 2024-10-17 14:21:39 +02:00
3262e70eac admin: store version history (#11520)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-10-17 14:09:44 +02:00
0976e05c7d web: bump API Client version (#11706)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-17 14:08:39 +02:00
47206d3328 providers/oauth2: add initial JWE support (#11344)
* providers/oauth2: add initial JWE support

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

* re-migrate, only set id_token_encryption_* when encryption key is set

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

* add docs

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

* add jwks test with encryption

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-17 14:04:19 +02:00
fc1f146049 core, web: update translations (#11703)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-17 13:18:01 +02:00
89f251d559 tests/e2e: add forward auth e2e test (#11374)
* add nginx forward_auth e2e tests

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

* add envoy

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

* cleanup

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

* remove even more duplicate code

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

* cleanup more

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

* add traefik static config

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

* more cleanup, don't generate dex config cause they support env variables

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

* use default dex entrypoint to use templating

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

* remove options that are always set as default

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

* fix

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

* fix compose flag

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

* add caddy

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

* merge python files

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

* use whoami api to check better

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

* fix envoy config

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

* set invalidation flow

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

* fix logout checks

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-16 18:01:59 +02:00
c4caef4c38 web/admin: fix duplicate flow labels (#11689)
* web/admin: fix duplicate flow labels

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

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-16 17:19:06 +02:00
6cc0a668e7 providers/saml: fix incorrect ds:Reference URI (#11699)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-16 17:17:05 +02:00
f531dd9520 website/docs: Fix websocket default config for nginx proxy manager (#11621)
* Comment out problematic config at _nginx_proxy_manager.md

Resolves:
- https://github.com/goauthentik/authentik/issues/10010
- https://github.com/goauthentik/authentik/discussions/7323
- https://github.com/goauthentik/authentik/issues/11453
- https://www.reddit.com/r/Authentik/comments/1c5sf6l/authentik_with_nginx_proxy_manager_not_possible/

Signed-off-by: Mahmoud AlyuDeen <mahmoudalyudeen@gmail.com>

* Add working websocket configuration for nginx-proxy-manager.

Signed-off-by: Mahmoud AlyuDeen <mahmoudalyudeen@gmail.com>

* remove commented out settings

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

---------

Signed-off-by: Mahmoud AlyuDeen <mahmoudalyudeen@gmail.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-10-16 17:02:02 +02:00
01e7124fac core, web: update translations (#11692)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-16 15:53:34 +02:00
74c5edb87e core: bump uvicorn from 0.31.1 to 0.32.0 (#11693)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.31.1 to 0.32.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.31.1...0.32.0)

---
updated-dependencies:
- dependency-name: uvicorn
  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>
2024-10-16 15:53:24 +02:00
3ee7431ce2 core: bump github.com/prometheus/client_golang from 1.20.4 to 1.20.5 (#11694)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.4 to 1.20.5.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.20.4...v1.20.5)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_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>
2024-10-16 15:53:15 +02:00
50087db63d website/docs: add missing file to sidebar (#11695)
add missing file to sidebar

Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-10-16 15:53:01 +02:00
2897c2313d website/docs: rewrote too long sentence (#11696)
rewrote too long sentence

Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-10-16 15:52:52 +02:00
afbbfa96ff translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#11697)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-16 15:52:42 +02:00
1b917ee670 translate: Updates for file web/xliff/en.xlf in fr (#11698)
Translate web/xliff/en.xlf in fr

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-10-16 15:52:28 +02:00
01f5d6fc0d stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#11683)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-15 17:58:50 +02:00
5696bcd39c core, web: update translations (#11682)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-10-15 17:58:40 +02:00
deaa5ddb46 core: bump github.com/getsentry/sentry-go from 0.29.0 to 0.29.1 (#11684)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.29.0 to 0.29.1.
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-go/compare/v0.29.0...v0.29.1)

---
updated-dependencies:
- dependency-name: github.com/getsentry/sentry-go
  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>
2024-10-15 17:58:27 +02:00
2e076d0170 core: bump github.com/redis/go-redis/v9 from 9.6.1 to 9.6.2 (#11685)
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.6.1 to 9.6.2.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.6.1...v9.6.2)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  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>
2024-10-15 17:58:19 +02:00
d88434c773 core: bump goauthentik.io/api/v3 from 3.2024083.7 to 3.2024083.8 (#11686)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024083.7 to 3.2024083.8.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2024083.7...v3.2024083.8)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  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>
2024-10-15 17:58:10 +02:00
4a76bb58f4 web: bump API Client version (#11680)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-10-14 21:18:21 +00:00
c42bfb0923 stages/password: add error message when exceeding maximum tries (#11679)
* stages/password: add error message when exceeding maximum tries

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

* Update authentik/stages/password/stage.py

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens L. <jens@beryju.org>

* fix tests

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

* fix sentry deprecation error

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

* bump go api

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-10-14 23:00:36 +02:00
10580d8aa9 web/admin: display webauthn device type (#11481)
* web/user,admin: display webauthn device type

Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix

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

* fix 2

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

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-10-14 22:57:45 +02:00
b73481d97f Merge branch 'main' into web/cleanup/20240516-simple-element-docs-1
* main:
  website: latest migration to new structure (#11522)
  web: Fix css loading in unit tests, remove unneeded dot paths (#11629)
  website: bump docusaurus-theme-openapi-docs from 4.0.1 to 4.1.0 in /website (#11624)
  core, web: update translations (#11623)
  website: bump docusaurus-plugin-openapi-docs from 4.0.1 to 4.1.0 in /website (#11625)
  core: bump ruff from 0.6.8 to 0.6.9 (#11626)
  ci: require ci-web.build for merging (#11627)
  core: bump black from 24.8.0 to 24.10.0 (#11630)
  core: bump google-api-python-client from 2.147.0 to 2.148.0 (#11631)
2024-10-09 13:39:28 -07:00
32db275015 Merge branch 'main' into web/cleanup/20240516-simple-element-docs-1
* main: (481 commits)
  web: provide simple tables for API-less displays (#11028)
  core: bump goauthentik/fips-python from 3.12.6-slim-bookworm-fips-full to 3.12.7-slim-bookworm-fips-full (#11607)
  core: bump twilio from 9.3.2 to 9.3.3 (#11608)
  core: bump msgraph-sdk from 1.8.0 to 1.9.0 (#11609)
  web: small fixes for elements and forms (#11546)
  web: unify unit and end-to-end tests (#11598)
  web: audit and update package.json and associated test harness, with upgrade to WebdriverIO 9 (#11596)
  web: bump @spotlightjs/spotlight from 2.4.1 to 2.4.2 in /web in the sentry group across 1 directory (#11581)
  core: bump sentry-sdk from 2.14.0 to 2.15.0 (#11589)
  web: bump the rollup group across 2 directories with 4 updates (#11590)
  web: bump chromedriver from 129.0.1 to 129.0.2 in /tests/wdio (#11591)
  web: bump globals from 15.9.0 to 15.10.0 in /web (#11592)
  core, web: update translations (#11575)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#11578)
  core: bump goauthentik.io/api/v3 from 3.2024083.1 to 3.2024083.2 (#11580)
  web: bump the eslint group across 2 directories with 4 updates (#11582)
  web: bump API Client version (#11574)
  stages/identification: dynamically find login challenges (#11571)
  web: add missing id attribute for button in ak-flow-input-password (#11413)
  internal: restore /ping behaviour for embedded outpost (#11568)
  ...
2024-10-08 09:11:21 -07:00
6650f6baa7 Merge regression fixed. 2024-08-06 14:47:25 -07:00
05145639ba Merge branch 'main' into web/cleanup/20240516-simple-element-docs-1
* main: (690 commits)
  blueprints: handle model referencing non-existent app/model (#10796)
  website/docs: add more content about flows (#10527)
  brands: add OIDC webfinger support (#10400)
  web/admin: fix selectable card colour in dark theme (#10794)
  web: bump API Client version (#10793)
  policies: add GeoIP policy (#10454)
  core: bump debugpy from 1.8.3 to 1.8.5 (#10781)
  web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782)
  website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783)
  web: bump the wdio group across 2 directories with 4 updates (#10785)
  web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786)
  web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787)
  web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788)
  web: bump lit from 3.1.4 to 3.2.0 in /web (#10789)
  core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790)
  web: bump API Client version (#10779)
  release: 2024.6.3
  website/docs: prepare 2024.6.3 release notes (#10775)
  website/scripts: updated readme, added docsmg.env file  (#10710)
  web: bump API Client version (#10777)
  ...
2024-08-06 14:42:22 -07:00
22a8ccfba6 Provide documentation and tests for the LoadingOverlay. 2024-05-17 15:01:59 -07:00
934f778b97 Merge branch 'dev' into web/cleanup/20240516-simple-element-docs-1
* dev:
  web: bump @sentry/browser from 7.114.0 to 8.2.1 in /web in the sentry group across 1 directory (#9757)
  core, web: update translations (#9714)
  core: bump sentry-sdk from 2.1.1 to 2.2.0 (#9753)
  core: bump selenium from 4.20.0 to 4.21.0 (#9754)
  core: bump msgraph-sdk from 1.2.0 to 1.4.0 (#9755)
  core: bump github.com/sethvargo/go-envconfig from 1.0.1 to 1.0.2 (#9756)
  web: bump chromedriver from 124.0.3 to 125.0.0 in /tests/wdio (#9758)
  website/docs: new PR for the Entra provider docs (ignore old one) (#9741)
2024-05-17 10:16:36 -07:00
5752497b4d Merge branch 'main' into dev
* main:
  web: bump @sentry/browser from 7.114.0 to 8.2.1 in /web in the sentry group across 1 directory (#9757)
  core, web: update translations (#9714)
  core: bump sentry-sdk from 2.1.1 to 2.2.0 (#9753)
  core: bump selenium from 4.20.0 to 4.21.0 (#9754)
  core: bump msgraph-sdk from 1.2.0 to 1.4.0 (#9755)
  core: bump github.com/sethvargo/go-envconfig from 1.0.1 to 1.0.2 (#9756)
  web: bump chromedriver from 124.0.3 to 125.0.0 in /tests/wdio (#9758)
  website/docs: new PR for the Entra provider docs (ignore old one) (#9741)
  root: include task_id in events and logs (#9749)
  web: bump the esbuild group in /web with 2 updates (#9745)
  web: bump esbuild from 0.21.2 to 0.21.3 in /web (#9746)
  web: bump the storybook group across 1 directory with 7 updates (#9747)
2024-05-17 10:12:19 -07:00
79d73b0d57 web: Document the labels. Fix the alerts
This commit documents and adds unit tests for our `Labels` component, which are usually called
"chips" in other design systems.

I've also reverted to allowing the components that take 'level' information to take it as a single
argument that is _either_ an attribute or a property.  If it's a property, it reverts to the older
behavior.
2024-05-17 09:52:26 -07:00
3797cc25be What happens when I don't run my own tests. 2024-05-16 15:32:04 -07:00
35f96df66e web: Testing and documenting the simple things
This commit adds unit tests for the Alerts and EmptyState elements. It includes a new
test/documentation feature; elements can now be fully documented with text description and active
controls.

It *removes* the `babel` imports entirely.  Either we don't need them, or the components that do
need them are importing them automatically.

[An outstanding bug in WebDriverIO](https://github.com/webdriverio/webdriverio/issues/12056)
unfortunately means that the tests cannot be run in parallel for the time being.While one test is
running, the compiler for other tests becomes unreliable. They're currently working on this issue.
I have set the `maxInstances` to **1**.

I have updated the `<ak-alert>` component just a bit, providing an attribute alternative to the
`Level` property; now instead of passing it a `<ak-alert level=${Levels.Warning}>` properties, you
can just say `<ak-alert warning>` and it'll work just fine. The old way is still the default
behavior.

The default behavior for `EmptyState` was a little confusing; I've re-arranged it for clarity. Since
I touched it, I also added the `interface` and `HTMLElementTagNameMap` declarations.

Added documentation to all the elements I've touched (so far).
2024-05-16 15:26:35 -07:00
9428fd866e Merge branch 'main' into web/cleanup/20240516-simple-element-docs-1
* main:
  root: include task_id in events and logs (#9749)
  web: bump the esbuild group in /web with 2 updates (#9745)
  web: bump esbuild from 0.21.2 to 0.21.3 in /web (#9746)
  web: bump the storybook group across 1 directory with 7 updates (#9747)
2024-05-16 10:54:43 -07:00
bd6651ebfe Prettier had opinions. 2024-05-15 16:05:54 -07:00
83bac1ff6d web: Provide tests for the aggregate cards, fix a few minor things
This commit provides tests alongside the stories for the aggregate cards. The tests are fairly
basic, but they're good enough for starting *and* they provide a pretty good example of how to test
when a promise with a delay is involved.

Two minor fixes in this code:

- The subtext was given a small amount of whitespace above, to remove the crowding that happened.
  It looks much better with a half-rem of space.
- In the rare case that we have a card header with no icon, the '&nbsp;' symbol that separates the
  icon from the header is now not rendered. In the previous form, it would push the header to the
  left, making it "hang in space" one rem to the right of the visual line formed by the rightmost
  content border.  The padding between the header, body, and footer is odd; body is 1 rem, the
  header and footer 2rems. This looks good for the graphs, but for the text, not so much.
2024-05-15 16:02:37 -07:00
04355d8c4e Merge branch 'web/automated-testing' into web/cleanup/admin-overview-testing
* web/automated-testing:
  Adjusting paths to work with tests.
  Rolling back a reversion.
  web: provide a test framework
2024-05-15 11:08:45 -07:00
41c362eaf9 Adjusting paths to work with tests. 2024-05-15 09:46:31 -07:00
c46fc3e100 A few documentation changes. 2024-05-15 08:21:40 -07:00
234d52de3b Added a ton of documentation; made the failure message configurable. 2024-05-14 16:38:44 -07:00
beab52d342 Slight revision in exception logic. 2024-05-14 14:27:02 -07:00
99f8802122 web: update storybook, storybook a few things, fix a few things
After examining how people like Adobe and Salesforce do things, I have updated the storybook
configuration to provide run-time configuration of light/dark mode (although right now nothing
happens), inject the correct styling into the page, and update the preview handling so that we can
see the components better.  We'll see how this pans out.

I have provided stories for the AggregateCard, AggregatePromiseCard, and a new QuickActionsCard. I
also fixed a bug in AggregatePromiseCard where it would fail to report a fetch error. It will only
report that "the operation falied," but it will give the full error into the console.

**As an experiment**, I have changed the interpreter for `lint:precommit` and `build:watch` to use
[Bun](https://bun.sh/) instead of NodeJS. We have observed significant speed-ups and much better
memory management with Bun for these two operations. Those are both developer-facing operations, the
behavior of the system undur current CI/CD should not change.

And finally, I've switched the QuickActionsCard view in Admin-Overview to use the new component.
Looks the same.  Reads *way* easier.  :-)
2024-05-14 14:17:15 -07:00
09803fee11 Merge branch 'main' into dev
* main:
  website/docs: update traefik to latest version in proxy provider (#9707)
  sources/saml: fix FlowPlanner error due to pickle (#9708)
  website/docs: add docs about Google Workspace (#9669)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9702)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9703)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9705)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9706)
2024-05-14 08:53:53 -07:00
9133ab6870 Merge branch 'dev' into web/automated-testing
* dev:
  web: bump esbuild from 0.21.1 to 0.21.2 in /web (#9696)
  web: bump chromedriver from 124.0.2 to 124.0.3 in /tests/wdio (#9692)
  website: bump @types/react from 18.3.1 to 18.3.2 in /website (#9691)
  core, web: update translations (#9672)
  core: bump psycopg from 3.1.18 to 3.1.19 (#9698)
  core: bump google-api-python-client from 2.128.0 to 2.129.0 (#9694)
  web: bump the esbuild group in /web with 2 updates (#9695)
  web: bump glob from 10.3.14 to 10.3.15 in /web (#9697)
  core: bump freezegun from 1.5.0 to 1.5.1 (#9693)
  web: fix value handling inside controlled components (#9648)
2024-05-13 08:40:01 -07:00
3fae9e5102 Merge branch 'main' into dev
* main:
  web: bump esbuild from 0.21.1 to 0.21.2 in /web (#9696)
  web: bump chromedriver from 124.0.2 to 124.0.3 in /tests/wdio (#9692)
  website: bump @types/react from 18.3.1 to 18.3.2 in /website (#9691)
  core, web: update translations (#9672)
  core: bump psycopg from 3.1.18 to 3.1.19 (#9698)
  core: bump google-api-python-client from 2.128.0 to 2.129.0 (#9694)
  web: bump the esbuild group in /web with 2 updates (#9695)
  web: bump glob from 10.3.14 to 10.3.15 in /web (#9697)
  core: bump freezegun from 1.5.0 to 1.5.1 (#9693)
  web: fix value handling inside controlled components (#9648)
2024-05-13 08:36:55 -07:00
ac8ecf163b Rolling back a reversion. 2024-05-10 09:47:30 -07:00
61717b10c7 web: provide a test framework
As is typical of a system where a new build engine is involved, this thing is sadly fragile. Use the
wrong import style in wdio.conf.js and it breaks; there are several notes in tsconfig.test.conf and
wdio.conf.ts to tell eslint or tsc not to complain, it's just a different build with different
criteria, the native criteria don't apply.

On the other hand, writing tests is easy and predictable. We can test behaviors at the unit and
component scale in a straightforward manner, and validate our expectations that things work the way
we believe they should.
2024-05-10 09:45:42 -07:00
fffc8c7c0c Merge branch 'main' into dev
* main:
  website/docs: add hardening advice and link directly to Cure53 results (#9670)
  core: bump goauthentik.io/api/v3 from 3.2024042.2 to 3.2024042.4 (#9674)
  core: bump ruff from 0.4.3 to 0.4.4 (#9677)
  core: bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1 (#9675)
  web: bump glob from 10.3.12 to 10.3.14 in /web (#9676)
  lib/providers/sync: multiple minor fixes (#9667)
  core: fix source flow_manager not always appending save stage (#9659)
  web: bump API Client version (#9660)
  web/admin: only show non-backchannel providers in application provider select (#9658)
  website/docs: add new doc about extra steps for hardening authentik (#9649)
2024-05-10 08:15:16 -07:00
3d532d4feb Merge branch 'main' into dev
* main:
  web: bump API Client version (#9656)
  enterprise/providers/microsoft_entra: initial account sync to microsoft entra (#9632)
  web: bump chromedriver from 124.0.1 to 124.0.2 in /tests/wdio (#9652)
  web: bump @sentry/browser from 7.113.0 to 7.114.0 in /web in the sentry group (#9653)
  core, web: update translations (#9650)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9644)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9646)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9645)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9647)
2024-05-09 07:25:30 -07:00
e1d565d40e Merge branch 'main' into dev
* main:
  core, web: update translations (#9633)
  core: bump google-api-python-client from 2.127.0 to 2.128.0 (#9641)
  core: bump goauthentik.io/api/v3 from 3.2024041.3 to 3.2024042.2 (#9635)
  core: bump golang from 1.22.2-bookworm to 1.22.3-bookworm (#9636)
  web: bump the esbuild group in /web with 2 updates (#9637)
  web: bump esbuild from 0.21.0 to 0.21.1 in /web (#9639)
  core: bump django from 5.0.5 to 5.0.6 (#9640)
  web: bump API Client version (#9630)
  enterprise/providers/google: initial account sync to google workspace (#9384)
  web/flows: fix error when using consecutive webauthn validator stages (#9629)
  web: bump API Client version (#9626)
  website/docs: refine intro page for sources (#9625)
  release: 2024.4.2
2024-05-08 08:06:05 -07:00
ee37e9235b Merge branch 'main' into dev
* main: (42 commits)
  website/docs: prepare 2024.4.2 release notes (#9555)
  web: bump the esbuild group in /web with 2 updates (#9616)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9611)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9560)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9563)
  ci: bump golangci/golangci-lint-action from 5 to 6 (#9615)
  web: bump esbuild from 0.20.2 to 0.21.0 in /web (#9617)
  core: bump cryptography from 42.0.6 to 42.0.7 (#9618)
  core: bump sentry-sdk from 2.0.1 to 2.1.1 (#9619)
  core: bump django from 5.0.4 to 5.0.5 (#9620)
  core, web: update translations (#9613)
  website/docs: move Sources from Integrations into Docs (#9515)
  website/docs: add procedurals to flow inspector docs (#9556)
  core: bump jinja2 from 3.1.3 to 3.1.4 (#9610)
  web: clean up the options rendering in PromptForm (#9564)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9608)
  website/docs: add instructions for deploying radius manually with docker compose (#9605)
  sources/scim: fix duplicate groups and invalid schema (#9466)
  core: fix condition in task clean_expiring_models (#9603)
  translate: Updates for file web/xliff/en.xlf in fr (#9600)
  ...
2024-05-07 08:49:15 -07:00
8248163958 Merge branch 'main' into dev
* main:
  website/docs: fix openssl rand commands (#9554)
  web: bump @sentry/browser from 7.112.2 to 7.113.0 in /web in the sentry group (#9549)
  core, web: update translations (#9548)
  core: bump goauthentik.io/api/v3 from 3.2024041.1 to 3.2024041.2 (#9551)
  core: bump django-model-utils from 4.5.0 to 4.5.1 (#9550)
  providers/scim: fix time_limit not set correctly (#9546)
2024-05-03 13:40:13 -07:00
9acebec1f6 Merge branch 'main' into dev
* main:
  web/flows: fix error when enrolling multiple WebAuthn devices consecutively (#9545)
  web: bump ejs from 3.1.9 to 3.1.10 in /tests/wdio (#9542)
  web: bump API Client version (#9543)
  providers/saml: fix ecdsa support (#9537)
  website/integrations: nextcloud: connect to existing user (#9155)
2024-05-03 08:22:55 -07:00
2a96900dc7 Merge branch 'main' into dev
* main: (43 commits)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#9535)
  web: bump the rollup group across 1 directory with 3 updates (#9532)
  website/developer-docs: Add note for custom YAML tags in an IDE (#9528)
  lifecycle: close database connection after migrating (#9516)
  web: bump the babel group in /web with 3 updates (#9520)
  core: bump node from 21 to 22 (#9521)
  web: bump @codemirror/lang-python from 6.1.5 to 6.1.6 in /web (#9523)
  providers/rac: bump guacd to 1.5.5 (#9514)
  core: only prefetch related objects when required (#9476)
  website/integrations: move Fortimanager to Networking (#9505)
  website: bump react-tooltip from 5.26.3 to 5.26.4 in /website (#9494)
  web: bump the rollup group in /web with 3 updates (#9497)
  web: bump yaml from 2.4.1 to 2.4.2 in /web (#9499)
  core: bump goauthentik.io/api/v3 from 3.2024040.1 to 3.2024041.1 (#9503)
  core: bump pytest from 8.1.1 to 8.2.0 (#9501)
  website: bump react-dom from 18.3.0 to 18.3.1 in /website (#9495)
  website: bump react and @types/react in /website (#9496)
  web: bump react-dom from 18.3.0 to 18.3.1 in /web (#9498)
  core: bump sentry-sdk from 2.0.0 to 2.0.1 (#9502)
  web/flows: fix missing fallback for flow logo (#9487)
  ...
2024-05-01 17:24:34 -07:00
ca42506fa0 Merge branch 'main' into dev
* main:
  web: clean up some repetitive types (#9241)
  core: fix logic for token expiration (#9426)
  ci: fix ci pipeline (#9427)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9424)
  web: Add resolved and integrity fields back to package-lock.json (#9419)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ru (#9407)
  stages/identification: don't check source component (#9410)
  core: bump selenium from 4.19.0 to 4.20.0 (#9411)
  core: bump black from 24.4.0 to 24.4.1 (#9412)
  ci: bump golangci/golangci-lint-action from 4 to 5 (#9413)
  core: bump goauthentik.io/api/v3 from 3.2024023.2 to 3.2024040.1 (#9414)
  web: bump @sentry/browser from 7.112.1 to 7.112.2 in /web in the sentry group (#9416)
  sources/oauth: ensure all UI sources return a valid source (#9401)
  web: markdown: display markdown even when frontmatter is missing (#9404)
2024-04-25 08:38:08 -07:00
34de6bfd3a Merge branch 'main' into dev
* main:
  web: bump API Client version (#9400)
  release: 2024.4.0
  release: 2024.4.0-rc1
  root: bump blueprint schema version
  lifecycle: fix ak test-all command
  website/docs: finalize 2024.4 release notes (#9396)
  web: bump @sentry/browser from 7.111.0 to 7.112.1 in /web in the sentry group (#9387)
  web: bump the rollup group in /web with 3 updates (#9388)
  ci: bump helm/kind-action from 1.9.0 to 1.10.0 (#9389)
  website: bump clsx from 2.1.0 to 2.1.1 in /website (#9390)
  core: bump pydantic from 2.7.0 to 2.7.1 (#9391)
  core: bump freezegun from 1.4.0 to 1.5.0 (#9393)
  core: bump coverage from 7.4.4 to 7.5.0 (#9392)
  web: bump the storybook group in /web with 7 updates (#9380)
  web: bump the rollup group in /web with 3 updates (#9381)
2024-04-24 13:20:02 -07:00
2d94b16411 Merge branch 'main' into dev
* main: (24 commits)
  web: bump the wdio group in /tests/wdio with 4 updates (#9374)
  web: bump the rollup group in /web with 3 updates (#9371)
  core: bump ruff from 0.4.0 to 0.4.1 (#9372)
  core, web: update translations (#9366)
  web/admin: fix document title for admin interface (#9362)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9363)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9364)
  core, web: update translations (#9360)
  website/docs: release notes 2024.4: add performance improvements values (#9356)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9317)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9318)
  website/docs: 2024.4 release notes (#9267)
  sources/ldap: fix default blueprint for mapping user DN to path (#9355)
  web/admin: group form dual select (#9354)
  core: bump golang.org/x/net from 0.22.0 to 0.23.0 (#9351)
  core: bump goauthentik.io/api/v3 from 3.2024023.1 to 3.2024023.2 (#9345)
  web: bump chromedriver from 123.0.3 to 123.0.4 in /tests/wdio (#9348)
  core: bump twilio from 9.0.4 to 9.0.5 (#9346)
  core: bump ruff from 0.3.7 to 0.4.0 (#9347)
  web: bump @sentry/browser from 7.110.1 to 7.111.0 in /web in the sentry group (#9349)
  ...
2024-04-22 08:53:56 -07:00
98503f6009 Merge branch 'main' into dev
* main:
  stages/prompt: fix username field throwing error with existing user (#9342)
  root: expose session storage configuration (#9337)
  website/integrations: fix typo (#9340)
  root: fix go.mod for codeql checking (#9338)
  root: make redis settings more consistent (#9335)
  web/admin: fix error in admin interface due to un-hydrated context (#9336)
  web: bump API Client version (#9334)
  stages/authenticator_webauthn: fix attestation value (#9333)
  website/docs: fix SECRET_KEY length (#9328)
  website/docs: fix email template formatting (#9330)
  core, web: update translations (#9323)
  web: bump @patternfly/elements from 3.0.0 to 3.0.1 in /web (#9324)
  core: bump celery from 5.3.6 to 5.4.0 (#9325)
  core: bump goauthentik.io/api/v3 from 3.2024022.12 to 3.2024023.1 (#9327)
  sources/scim: service account should be internal (#9321)
  web: bump the storybook group in /web with 8 updates (#9266)
  sources/scim: cleanup service account when source is deleted (#9319)
2024-04-18 11:55:29 -07:00
ac4ba5d9e2 Merge branch 'main' into dev
* main: (23 commits)
  web: bump API Client version (#9316)
  release: 2024.2.3
  website/docs: 2024.2.3 release notes (#9313)
  web/admin: fix log viewer empty state (#9315)
  website/docs: fix formatting for stage changes (#9314)
  core: bump github.com/go-ldap/ldap/v3 from 3.4.7 to 3.4.8 (#9310)
  core: bump goauthentik.io/api/v3 from 3.2024022.11 to 3.2024022.12 (#9311)
  web: bump core-js from 3.36.1 to 3.37.0 in /web (#9309)
  core: bump gunicorn from 21.2.0 to 22.0.0 (#9308)
  core, web: update translations (#9307)
  website/docs: system settings: add default token duration and length (#9306)
  web/flows: update flow background (#9305)
  web: fix locale loading being skipped (#9301)
  translate: Updates for file web/xliff/en.xlf in fr (#9304)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#9303)
  core: replace authentik_signals_ignored_fields with audit_ignore (#9291)
  web/flow: fix form input rendering issue (#9297)
  events: fix incorrect user logged when using API token authentication (#9302)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9293)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9295)
  ...
2024-04-17 10:50:26 -07:00
f19ed14bf8 Merge branch 'main' into dev
* main: (34 commits)
  web: bump API Client version (#9299)
  core: fix api schema for users and groups (#9298)
  providers/oauth2: fix refresh_token grant returning incorrect id_token (#9275)
  web: bump @sentry/browser from 7.110.0 to 7.110.1 in /web in the sentry group (#9278)
  core, web: update translations (#9277)
  web: bump the rollup group in /web with 3 updates (#9280)
  web: bump lit from 3.1.2 to 3.1.3 in /web (#9282)
  web: bump @lit/context from 1.1.0 to 1.1.1 in /web (#9281)
  website: bump @types/react from 18.2.78 to 18.2.79 in /website (#9286)
  core: bump goauthentik.io/api/v3 from 3.2024022.10 to 3.2024022.11 (#9285)
  core: bump sqlparse from 0.4.4 to 0.5.0 (#9276)
  lifecycle: gunicorn: fix app preload (#9274)
  events: add indexes (#9272)
  web/flows: fix passwordless hidden without input (#9273)
  root: fix geoipupdate arguments (#9271)
  website/docs: cleanup more (#9249)
  web: bump API Client version (#9270)
  sources: add SCIM source (#3051)
  core: delegated group member management (#9254)
  web: bump API Client version (#9269)
  ...
2024-04-16 10:49:58 -07:00
085debf170 Merge branch 'main' into dev
* main: (21 commits)
  web: manage stacked modals with a stack (#9193)
  website/docs: ensure yaml code blocks have language tags (#9240)
  blueprints: only create default brand if no other default brand exists (#9222)
  web: bump API Client version (#9239)
  website/integrations: portainer: Fix Redirect URL mismatch (#9226)
  api: fix authentication schema (#9238)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9229)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9230)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9228)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9231)
  core: bump pydantic from 2.6.4 to 2.7.0 (#9232)
  core: bump ruff from 0.3.5 to 0.3.7 (#9233)
  web: bump @sentry/browser from 7.109.0 to 7.110.0 in /web in the sentry group (#9234)
  website: bump @types/react from 18.2.75 to 18.2.77 in /website (#9236)
  core, web: update translations (#9225)
  website/integrations: add pfSense search scope (#9221)
  core: bump idna from 3.6 to 3.7 (#9224)
  website/docs: add websocket support to nginx snippets (#9220)
  internal: add tests to go flow executor (#9219)
  website/integrations: nextcloud: add tip to solve hashed groups configuring OAuth2 (#9153)
  ...
2024-04-12 14:27:20 -07:00
cacdf64408 Merge branch 'main' into dev
* main:
  website/docs: add more info and links about enforciing unique email addresses (#9154)
  core: bump goauthentik.io/api/v3 from 3.2024022.7 to 3.2024022.8 (#9215)
  web: bump API Client version (#9214)
  stages/authenticator_validate: add ability to limit webauthn device types (#9180)
  web: bump API Client version (#9213)
  core: add user settable token durations (#7410)
  core, web: update translations (#9205)
  web: bump typescript from 5.4.4 to 5.4.5 in /tests/wdio (#9206)
  web: bump chromedriver from 123.0.2 to 123.0.3 in /tests/wdio (#9207)
  core: bump sentry-sdk from 1.44.1 to 1.45.0 (#9208)
  web: bump typescript from 5.4.4 to 5.4.5 in /web (#9209)
  website: bump typescript from 5.4.4 to 5.4.5 in /website (#9210)
  core: bump python from 3.12.2-slim-bookworm to 3.12.3-slim-bookworm (#9211)
2024-04-11 08:10:41 -07:00
23665d173f Merge branch 'main' into dev
* main:
  website/docs: add note for flow compatibility mode (#9204)
2024-04-10 13:53:58 -07:00
272fdc516b Merge branch 'main' into dev
* main:
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9194)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9197)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9196)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9198)
  web: preserve selected list when provider updates (#9200)
  web: bump API Client version (#9195)
  sources/oauth: make URLs not required, only check when no OIDC URLs are defined (#9182)
2024-04-10 08:17:38 -07:00
b08dcc2289 Merge branch 'main' into dev
* main:
  web/admin: fix SAML Provider preview (#9192)
  core, web: update translations (#9183)
  web: bump chromedriver from 123.0.1 to 123.0.2 in /tests/wdio (#9188)
  website: bump @types/react from 18.2.74 to 18.2.75 in /website (#9185)
  website/docs: update Postgresql username (#9190)
  core: bump maxmind/geoipupdate from v6.1 to v7.0 (#9186)
  events: add context manager to ignore/modify audit events being written (#9181)
  web: fix application library list display length and capability (#9094)
2024-04-09 08:47:08 -07:00
c84be1d961 Merge branch 'main' into dev
* main: (25 commits)
  root: fix readme (#9178)
  enterprise: fix audit middleware import (#9177)
  web: bump @spotlightjs/spotlight from 1.2.16 to 1.2.17 in /web in the sentry group (#9162)
  web: bump API Client version (#9174)
  stages/authenticator_webauthn: add MDS support (#9114)
  website/integrations: Update Nextcloud OIDC secret size limitation (#9139)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9170)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9171)
  web: bump the rollup group in /web with 3 updates (#9164)
  web: bump @codemirror/legacy-modes from 6.3.3 to 6.4.0 in /web (#9166)
  web: bump ts-pattern from 5.1.0 to 5.1.1 in /web (#9167)
  core: bump github.com/go-ldap/ldap/v3 from 3.4.6 to 3.4.7 (#9168)
  core, web: update translations (#9156)
  root: fix redis username in lifecycle (#9158)
  web: ak-checkbox-group for short, static, multi-select events (#9138)
  root: fix startup (#9151)
  core: Bump golang.org/x/oauth2 from 0.18.0 to 0.19.0 (#9146)
  core: Bump twilio from 9.0.3 to 9.0.4 (#9143)
  web: Bump country-flag-icons from 1.5.10 to 1.5.11 in /web (#9144)
  web: Bump typescript from 5.4.3 to 5.4.4 in /web (#9145)
  ...
2024-04-08 09:22:54 -07:00
875fc5c735 Merge branch 'main' into dev
* main: (22 commits)
  blueprints: fix default username field in user-settings flow (#9136)
  website/docs: add procedural docs for RAC (#9006)
  web: bump API Client version (#9133)
  ci: fix python client generator (#9134)
  root: generate python client (#9107)
  web: Bump vite from 5.1.4 to 5.2.8 in /web (#9120)
  core, web: update translations (#9124)
  core: Bump golang from 1.22.1-bookworm to 1.22.2-bookworm (#9125)
  web: Bump the babel group in /web with 2 updates (#9126)
  web: Bump the eslint group in /web with 1 update (#9127)
  web: Bump the eslint group in /tests/wdio with 1 update (#9129)
  core: Bump sentry-sdk from 1.44.0 to 1.44.1 (#9130)
  core: Bump channels from 4.0.0 to 4.1.0 (#9131)
  core: Bump django from 5.0.3 to 5.0.4 (#9132)
  web: Bump the rollup group in /web with 3 updates (#9128)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#9110)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#9109)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#9111)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#9112)
  web: Bump @fortawesome/fontawesome-free from 6.5.1 to 6.5.2 in /web (#9116)
  ...
2024-04-04 10:54:11 -07:00
66cefcc918 Merge branch 'main' into dev
* main:
  root: fix missing imports after #9081 (#9106)
  root: move database calls from ready() to dedicated startup signal (#9081)
  web: fix console log leftover (#9096)
  web: bump the eslint group in /web with 2 updates (#9098)
  core: bump twilio from 9.0.2 to 9.0.3 (#9103)
  web: bump the eslint group in /tests/wdio with 2 updates (#9099)
  core: bump drf-spectacular from 0.27.1 to 0.27.2 (#9100)
  core: bump django-model-utils from 4.4.0 to 4.5.0 (#9101)
  core: bump ruff from 0.3.4 to 0.3.5 (#9102)
  website/docs:  update notes on SECRET_KEY (#9091)
  web: fix broken locale compile (#9095)
  website/integrations: add outline knowledge base (#8786)
  website/docs: fix typo (#9082)
  website/docs: email stage: fix example translation error (#9048)
2024-04-02 09:01:01 -07:00
5d4c38032f Merge branch 'main' into dev
* main:
  web: bump @patternfly/elements from 2.4.0 to 3.0.0 in /web (#9089)
  web: bump ts-pattern from 5.0.8 to 5.1.0 in /web (#9090)
  website: bump the docusaurus group in /website with 9 updates (#9087)
  web/admin: allow custom sorting for bound* tables (#9080)
2024-04-01 08:31:33 -07:00
7123b2c57b Merge branch 'main' into dev
* main:
  web: move context controllers into reactive controller plugins (#8996)
  web: maintenance: split tsconfig into “base” and “build” variants. (#9036)
  web: consistent style declarations internally (#9077)
2024-03-29 13:02:47 -07:00
fc00bdee63 Merge branch 'main' into dev
* main: (23 commits)
  providers/oauth2: fix interactive device flow (#9076)
  website/docs: fix transports example (#9074)
  events: fix log_capture (#9075)
  web: bump the sentry group in /web with 2 updates (#9065)
  core: bump goauthentik.io/api/v3 from 3.2024022.6 to 3.2024022.7 (#9064)
  web: bump @codemirror/lang-python from 6.1.4 to 6.1.5 in /web (#9068)
  web: bump the eslint group in /web with 1 update (#9066)
  web: bump glob from 10.3.10 to 10.3.12 in /web (#9069)
  web: bump the rollup group in /web with 3 updates (#9067)
  web: bump the eslint group in /tests/wdio with 1 update (#9071)
  core: bump webauthn from 2.0.0 to 2.1.0 (#9070)
  core: bump sentry-sdk from 1.43.0 to 1.44.0 (#9073)
  core: bump requests-mock from 1.12.0 to 1.12.1 (#9072)
  web: bump API Client version (#9061)
  events: rework log messages returned from API and their rendering (#8770)
  website/docs: update airgapped config (#9049)
  website: bump @types/react from 18.2.72 to 18.2.73 in /website (#9052)
  web: bump the rollup group in /web with 3 updates (#9053)
  core: bump django-filter from 24.1 to 24.2 (#9055)
  core: bump requests-mock from 1.11.0 to 1.12.0 (#9056)
  ...
2024-03-29 08:35:41 -07:00
a056703da0 Merge branch 'main' into dev
* main:
  web: a few minor bugfixes and lintfixes (#9044)
  website/integrations: add documentation for OIDC setup with Xen Orchestra (#9000)
  website: bump @types/react from 18.2.70 to 18.2.72 in /website (#9041)
  core: bump goauthentik.io/api/v3 from 3.2024022.5 to 3.2024022.6 (#9042)
  web: fix markdown rendering bug for alerts (#9037)
2024-03-27 10:51:02 -07:00
3f9502072d Merge branch 'main' into dev
* main:
  web: bump API Client version (#9035)
  website/docs: maintenance, re-add system settings (#9026)
  core: bump duo-client from 5.2.0 to 5.3.0 (#9029)
  website: bump express from 4.18.2 to 4.19.2 in /website (#9027)
  web: bump express from 4.18.3 to 4.19.2 in /web (#9028)
  web: bump the eslint group in /web with 2 updates (#9030)
  core: bump goauthentik.io/api/v3 from 3.2024022.3 to 3.2024022.5 (#9031)
  website: bump @types/react from 18.2.69 to 18.2.70 in /website (#9032)
  web: bump the eslint group in /tests/wdio with 2 updates (#9033)
  web: bump katex from 0.16.9 to 0.16.10 in /web (#9025)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#9023)
  website/docs: include OS-specific docker-compose install instructions + minor fixes (#8975)
2024-03-26 08:58:18 -07:00
2d254d6a7e Merge branch 'main' into dev
* main:
  web: bump API Client version (#9021)
  sources/ldap: add ability to disable password write on login (#8377)
  web: bump API Client version (#9020)
  lifecycle: migrate: ensure template schema exists before migrating (#8952)
  website/integrations: Update nextcloud Admin Group Expression (#7314)
  web/flow: general ux improvements (#8558)
  website: bump @types/react from 18.2.67 to 18.2.69 in /website (#9016)
  core: bump requests-oauthlib from 1.4.0 to 2.0.0 (#9018)
  web: bump the sentry group in /web with 2 updates (#9017)
  web/admin: small fixes (#9002)
  website: bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /website (#9001)
  core: bump ruff from 0.3.3 to 0.3.4 (#8998)
  website/docs: Upgrade nginx reverse porxy config (#8947)
  website/docs: improve flow inspector docs (#8993)
  website/deverlop-docs website/integrations: add links to integrations template (#8995)
2024-03-25 07:44:17 -07:00
a7e3dca917 Merge branch 'main' into dev
* main:
  website/docs: add example policy to enforce unique email address (#8955)
  web/admin: remove enterprise preview banner (#8991)
  core: bump uvicorn from 0.28.1 to 0.29.0 (#8980)
  core: bump sentry-sdk from 1.42.0 to 1.43.0 (#8981)
  web: bump the babel group in /web with 3 updates (#8983)
  web: bump typescript from 5.4.2 to 5.4.3 in /web (#8984)
  web: bump typescript from 5.4.2 to 5.4.3 in /tests/wdio (#8986)
  web: bump chromedriver from 122.0.6 to 123.0.0 in /tests/wdio (#8987)
  website: bump typescript from 5.4.2 to 5.4.3 in /website (#8989)
  core: bump importlib-metadata from 7.0.2 to 7.1.0 (#8982)
  web: bump the wdio group in /tests/wdio with 3 updates (#8985)
  website: bump postcss from 8.4.37 to 8.4.38 in /website (#8988)
2024-03-21 09:10:21 -07:00
5d8408287f Merge branch 'main' into dev
* main:
  website/docs: config: remove options moved to tenants (#8976)
  web: bump @types/grecaptcha from 3.0.8 to 3.0.9 in /web (#8971)
  web: bump country-flag-icons from 1.5.9 to 1.5.10 in /web (#8970)
  web: bump the babel group in /web with 7 updates (#8969)
  core: bump uvicorn from 0.28.0 to 0.28.1 (#8968)
  website: bump postcss from 8.4.36 to 8.4.37 in /website (#8967)
  internal: cleanup static file serving setup code (#8965)
  website/integrations: portainer: match portainer settings order (#8974)
2024-03-20 10:12:34 -07:00
30beca9118 Merge branch 'main' into dev
* main:
  web: improve build speeds even moar!!!!!! (#8954)
2024-03-19 14:37:17 -07:00
8946b81dbd Merge branch 'main' into dev
* main:
  outposts/proxy: Fix invalid redirect on external hosts containing path components (#8915)
  core: cache user application list under policies (#8895)
  web: bump the eslint group in /web with 2 updates (#8959)
  web: bump core-js from 3.36.0 to 3.36.1 in /web (#8960)
  website: bump @types/react from 18.2.66 to 18.2.67 in /website (#8962)
  web: bump the eslint group in /tests/wdio with 2 updates (#8963)
2024-03-19 14:36:12 -07:00
db96e1a901 Merge branch 'main' into dev
* main: (31 commits)
  root: support redis username (#8935)
  core: bump black from 24.2.0 to 24.3.0 (#8945)
  web: bump the wdio group in /tests/wdio with 2 updates (#8939)
  web: bump the sentry group in /web with 1 update (#8941)
  website: bump postcss from 8.4.35 to 8.4.36 in /website (#8940)
  core: bump twilio from 9.0.1 to 9.0.2 (#8942)
  core: bump ruff from 0.3.2 to 0.3.3 (#8943)
  events: discard notification if user has empty email (#8938)
  ci: always run ci-main on branch pushes (#8950)
  core: bump goauthentik.io/api/v3 from 3.2024022.2 to 3.2024022.3 (#8946)
  website/docs: add new name "Microsft Entra ID" for Azure AD  (#8930)
  outposts: Enhance config options for k8s outposts (#7363)
  website/docs: add link to CRUD docs (#8925)
  web: bump API Client version (#8927)
  outpost: improved set secret answers for flow execution (#8013)
  stages/user_write: ensure user data is json-serializable (#8926)
  website/docs: update example ldapsearch commands (#8906)
  admin: Handle latest  version unknown in admin dashboard (#8858)
  core: bump coverage from 7.4.3 to 7.4.4 (#8917)
  core: bump urllib3 from 1.26.18 to 2.2.1 (#8918)
  ...
2024-03-18 07:58:44 -07:00
8b4e0361c4 Merge branch 'main' into dev
* main:
  web: clean up and remove redundant alias '@goauthentik/app' (#8889)
  web/admin: fix markdown table rendering (#8908)
2024-03-14 10:35:46 -07:00
22cb5b7379 Merge branch 'main' into dev
* main:
  web: bump chromedriver from 122.0.5 to 122.0.6 in /tests/wdio (#8902)
  web: bump vite-tsconfig-paths from 4.3.1 to 4.3.2 in /web (#8903)
  core: bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#8901)
  web: provide InstallID on EnterpriseListPage (#8898)
2024-03-14 08:14:43 -07:00
2d0117d096 Merge branch 'main' into dev
* main:
  api: capabilities: properly set can_save_media when s3 is enabled (#8896)
  web: bump the rollup group in /web with 3 updates (#8891)
  core: bump pydantic from 2.6.3 to 2.6.4 (#8892)
  core: bump twilio from 9.0.0 to 9.0.1 (#8893)
2024-03-13 14:05:11 -07:00
035bda4eac Merge branch 'main' into dev
* main:
  Update _envoy_istio.md (#8888)
  website/docs: new landing page for Providers (#8879)
  web: bump the sentry group in /web with 1 update (#8881)
  web: bump chromedriver from 122.0.4 to 122.0.5 in /tests/wdio (#8884)
  web: bump the eslint group in /tests/wdio with 2 updates (#8883)
  web: bump the eslint group in /web with 2 updates (#8885)
  website: bump @types/react from 18.2.64 to 18.2.65 in /website (#8886)
2024-03-12 13:31:35 -07:00
50906214e5 Merge branch 'main' into dev
* main:
  web: upgrade to lit 3 (#8781)
2024-03-11 11:03:04 -07:00
e505f274b6 Merge branch 'main' into dev
* main:
  web: fix esbuild issue with style sheets (#8856)
2024-03-11 10:28:05 -07:00
fe52f44dca Merge branch 'main' into dev
* main:
  tenants: really ensure default tenant cannot be deleted (#8875)
  core: bump github.com/go-openapi/runtime from 0.27.2 to 0.28.0 (#8867)
  core: bump pytest from 8.0.2 to 8.1.1 (#8868)
  core: bump github.com/go-openapi/strfmt from 0.22.2 to 0.23.0 (#8869)
  core: bump bandit from 1.7.7 to 1.7.8 (#8870)
  core: bump packaging from 23.2 to 24.0 (#8871)
  core: bump ruff from 0.3.1 to 0.3.2 (#8873)
  web: bump the wdio group in /tests/wdio with 3 updates (#8865)
  core: bump requests-oauthlib from 1.3.1 to 1.4.0 (#8866)
  core: bump uvicorn from 0.27.1 to 0.28.0 (#8872)
  core: bump django-filter from 23.5 to 24.1 (#8874)
2024-03-11 10:27:43 -07:00
3146e5a50f web: fix esbuild issue with style sheets
Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.
2024-03-08 14:15:55 -08:00
294 changed files with 24973 additions and 2125 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2024.8.3
current_version = 2024.10.0
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -14,7 +14,7 @@ runs:
run: |
pipx install poetry || true
sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
- name: Setup python and restore poetry
uses: actions/setup-python@v5
with:

View File

@ -180,7 +180,7 @@ jobs:
uses: ./.github/actions/setup
- name: Setup e2e env (chrome, etc)
run: |
docker compose -f tests/e2e/docker-compose.yml up -d
docker compose -f tests/e2e/docker-compose.yml up -d --quiet-pull
- id: cache-web
uses: actions/cache@v4
with:

View File

@ -6,6 +6,7 @@
"authn",
"entra",
"goauthentik",
"jwe",
"jwks",
"kubernetes",
"oidc",

View File

@ -110,7 +110,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa
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 libpq-dev
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev libkrb5-dev
RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
@ -141,7 +141,7 @@ WORKDIR /
# We cannot cache this layer otherwise we'll end up with a bigger image
RUN apt-get update && \
# Required for runtime
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates libkrb5-3 libkadm5clnt-mit12 libkdb5-10 && \
# Required for bootstrap & healtcheck
apt-get install -y --no-install-recommends runit && \
apt-get clean && \
@ -161,6 +161,7 @@ COPY ./tests /tests
COPY ./manage.py /
COPY ./blueprints /blueprints
COPY ./lifecycle/ /lifecycle
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
COPY --from=go-builder /go/authentik /bin/authentik
COPY --from=python-deps /ak-root/venv /ak-root/venv
COPY --from=web-builder /work/web/dist/ /web/dist/

View File

@ -18,10 +18,10 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
(.x being the latest patch release for each version)
| Version | Supported |
| -------- | --------- |
| 2024.6.x | ✅ |
| 2024.8.x | ✅ |
| Version | Supported |
| --------- | --------- |
| 2024.8.x | ✅ |
| 2024.10.x | ✅ |
## Reporting a Vulnerability

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2024.8.3"
__version__ = "2024.10.0"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -0,0 +1,33 @@
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.admin.models import VersionHistory
from authentik.core.api.utils import ModelSerializer
class VersionHistorySerializer(ModelSerializer):
"""VersionHistory Serializer"""
class Meta:
model = VersionHistory
fields = [
"id",
"timestamp",
"version",
"build",
]
class VersionHistoryViewSet(ReadOnlyModelViewSet):
"""VersionHistory Viewset"""
queryset = VersionHistory.objects.all()
serializer_class = VersionHistorySerializer
permission_classes = [IsAdminUser]
filterset_fields = [
"version",
"build",
]
search_fields = ["version", "build"]
ordering = ["-timestamp"]
pagination_class = None

22
authentik/admin/models.py Normal file
View File

@ -0,0 +1,22 @@
"""authentik admin models"""
from django.db import models
from django.utils.translation import gettext_lazy as _
class VersionHistory(models.Model):
id = models.BigAutoField(primary_key=True)
timestamp = models.DateTimeField()
version = models.TextField()
build = models.TextField()
class Meta:
managed = False
db_table = "authentik_version_history"
ordering = ("-timestamp",)
verbose_name = _("Version history")
verbose_name_plural = _("Version history")
default_permissions = []
def __str__(self):
return f"{self.version}.{self.build} ({self.timestamp})"

View File

@ -6,6 +6,7 @@ from authentik.admin.api.meta import AppsViewSet, ModelViewSet
from authentik.admin.api.metrics import AdministrationMetricsViewSet
from authentik.admin.api.system import SystemView
from authentik.admin.api.version import VersionView
from authentik.admin.api.version_history import VersionHistoryViewSet
from authentik.admin.api.workers import WorkerView
api_urlpatterns = [
@ -17,6 +18,7 @@ api_urlpatterns = [
name="admin_metrics",
),
path("admin/version/", VersionView.as_view(), name="admin_version"),
("admin/version/history", VersionHistoryViewSet, "version_history"),
path("admin/workers/", WorkerView.as_view(), name="admin_workers"),
path("admin/system/", SystemView.as_view(), name="admin_system"),
]

View File

@ -51,6 +51,10 @@ from authentik.enterprise.providers.microsoft_entra.models import (
MicrosoftEntraProviderUser,
)
from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
EndpointDevice,
EndpointDeviceConnection,
)
from authentik.events.logs import LogEvent, capture_logs
from authentik.events.models import SystemTask
from authentik.events.utils import cleanse_dict
@ -119,6 +123,8 @@ def excluded_models() -> list[type[Model]]:
GoogleWorkspaceProviderGroup,
MicrosoftEntraProviderUser,
MicrosoftEntraProviderGroup,
EndpointDevice,
EndpointDeviceConnection,
)

View File

@ -6,34 +6,45 @@ from rest_framework.fields import (
BooleanField,
CharField,
DateTimeField,
IntegerField,
SerializerMethodField,
)
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
from authentik.rbac.decorators import permission_required
from authentik.stages.authenticator import device_classes, devices_for_user
from authentik.stages.authenticator.models import Device
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
class DeviceSerializer(MetaNameSerializer):
"""Serializer for Duo authenticator devices"""
pk = IntegerField()
pk = CharField()
name = CharField()
type = SerializerMethodField()
confirmed = BooleanField()
created = DateTimeField(read_only=True)
last_updated = DateTimeField(read_only=True)
last_used = DateTimeField(read_only=True, allow_null=True)
extra_description = SerializerMethodField()
def get_type(self, instance: Device) -> str:
"""Get type of device"""
return instance._meta.label
def get_extra_description(self, instance: Device) -> str:
"""Get extra description"""
if isinstance(instance, WebAuthnDevice):
return instance.device_type.description
if isinstance(instance, EndpointDevice):
return instance.data.get("deviceSignals", {}).get("deviceModel")
return ""
class DeviceViewSet(ViewSet):
"""Viewset for authenticator devices"""
@ -52,7 +63,7 @@ class AdminDeviceViewSet(ViewSet):
"""Viewset for authenticator devices"""
serializer_class = DeviceSerializer
permission_classes = [IsAdminUser]
permission_classes = []
def get_devices(self, **kwargs):
"""Get all devices in all child classes"""
@ -70,6 +81,10 @@ class AdminDeviceViewSet(ViewSet):
],
responses={200: DeviceSerializer(many=True)},
)
@permission_required(
None,
[f"{model._meta.app_label}.view_{model._meta.model_name}" for model in device_classes()],
)
def list(self, request: Request) -> Response:
"""Get all devices for current user"""
kwargs = {}

View File

@ -4,6 +4,7 @@ import code
import platform
import sys
import traceback
from pprint import pprint
from django.apps import apps
from django.core.management.base import BaseCommand
@ -34,7 +35,9 @@ class Command(BaseCommand):
def get_namespace(self):
"""Prepare namespace with all models"""
namespace = {}
namespace = {
"pprint": pprint,
}
# Gather Django models and constants from each app
for app in apps.get_app_configs():

View File

@ -330,11 +330,13 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
"""superuser == staff user"""
return self.is_superuser # type: ignore
def set_password(self, raw_password, signal=True):
def set_password(self, raw_password, signal=True, sender=None):
if self.pk and signal:
from authentik.core.signals import password_changed
password_changed.send(sender=self, user=self, password=raw_password)
if not sender:
sender = self
password_changed.send(sender=sender, user=self, password=raw_password)
self.password_change_date = now()
return super().set_password(raw_password)

View File

@ -1,11 +1,9 @@
"""Source decision helper"""
from enum import Enum
from typing import Any
from django.contrib import messages
from django.db import IntegrityError, transaction
from django.db.models.query_utils import Q
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
@ -16,12 +14,11 @@ from authentik.core.models import (
Group,
GroupSourceConnection,
Source,
SourceGroupMatchingModes,
SourceUserMatchingModes,
User,
UserSourceConnection,
)
from authentik.core.sources.mapper import SourceMapper
from authentik.core.sources.matcher import Action, SourceMatcher
from authentik.core.sources.stage import (
PLAN_CONTEXT_SOURCES_CONNECTION,
PostSourceStage,
@ -54,16 +51,6 @@ SESSION_KEY_OVERRIDE_FLOW_TOKEN = "authentik/flows/source_override_flow_token"
PLAN_CONTEXT_SOURCE_GROUPS = "source_groups"
class Action(Enum):
"""Actions that can be decided based on the request
and source settings"""
LINK = "link"
AUTH = "auth"
ENROLL = "enroll"
DENY = "deny"
class MessageStage(StageView):
"""Show a pre-configured message after the flow is done"""
@ -86,6 +73,7 @@ class SourceFlowManager:
source: Source
mapper: SourceMapper
matcher: SourceMatcher
request: HttpRequest
identifier: str
@ -108,6 +96,9 @@ class SourceFlowManager:
) -> None:
self.source = source
self.mapper = SourceMapper(self.source)
self.matcher = SourceMatcher(
self.source, self.user_connection_type, self.group_connection_type
)
self.request = request
self.identifier = identifier
self.user_info = user_info
@ -131,66 +122,19 @@ class SourceFlowManager:
def get_action(self, **kwargs) -> tuple[Action, UserSourceConnection | None]: # noqa: PLR0911
"""decide which action should be taken"""
new_connection = self.user_connection_type(source=self.source, identifier=self.identifier)
# When request is authenticated, always link
if self.request.user.is_authenticated:
new_connection = self.user_connection_type(
source=self.source, identifier=self.identifier
)
new_connection.user = self.request.user
new_connection = self.update_user_connection(new_connection, **kwargs)
return Action.LINK, new_connection
existing_connections = self.user_connection_type.objects.filter(
source=self.source, identifier=self.identifier
)
if existing_connections.exists():
connection = existing_connections.first()
return Action.AUTH, self.update_user_connection(connection, **kwargs)
# No connection exists, but we match on identifier, so enroll
if self.source.user_matching_mode == SourceUserMatchingModes.IDENTIFIER:
# We don't save the connection here cause it doesn't have a user assigned yet
return Action.ENROLL, self.update_user_connection(new_connection, **kwargs)
# Check for existing users with matching attributes
query = Q()
# Either query existing user based on email or username
if self.source.user_matching_mode in [
SourceUserMatchingModes.EMAIL_LINK,
SourceUserMatchingModes.EMAIL_DENY,
]:
if not self.user_properties.get("email", None):
self._logger.warning("Refusing to use none email")
return Action.DENY, None
query = Q(email__exact=self.user_properties.get("email", None))
if self.source.user_matching_mode in [
SourceUserMatchingModes.USERNAME_LINK,
SourceUserMatchingModes.USERNAME_DENY,
]:
if not self.user_properties.get("username", None):
self._logger.warning("Refusing to use none username")
return Action.DENY, None
query = Q(username__exact=self.user_properties.get("username", None))
self._logger.debug("trying to link with existing user", query=query)
matching_users = User.objects.filter(query)
# No matching users, always enroll
if not matching_users.exists():
self._logger.debug("no matching users found, enrolling")
return Action.ENROLL, self.update_user_connection(new_connection, **kwargs)
user = matching_users.first()
if self.source.user_matching_mode in [
SourceUserMatchingModes.EMAIL_LINK,
SourceUserMatchingModes.USERNAME_LINK,
]:
new_connection.user = user
new_connection = self.update_user_connection(new_connection, **kwargs)
return Action.LINK, new_connection
if self.source.user_matching_mode in [
SourceUserMatchingModes.EMAIL_DENY,
SourceUserMatchingModes.USERNAME_DENY,
]:
self._logger.info("denying source because user exists", user=user)
return Action.DENY, None
# Should never get here as default enroll case is returned above.
return Action.DENY, None # pragma: no cover
action, connection = self.matcher.get_user_action(self.identifier, self.user_properties)
if connection:
connection = self.update_user_connection(connection, **kwargs)
return action, connection
def update_user_connection(
self, connection: UserSourceConnection, **kwargs
@ -328,7 +272,6 @@ class SourceFlowManager:
connection: UserSourceConnection,
) -> HttpResponse:
"""Login user and redirect."""
flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user}
return self._prepare_flow(
self.source.authentication_flow,
connection,
@ -342,7 +285,11 @@ class SourceFlowManager:
),
)
],
**flow_kwargs,
**{
PLAN_CONTEXT_PENDING_USER: connection.user,
PLAN_CONTEXT_PROMPT: delete_none_values(self.user_properties),
PLAN_CONTEXT_USER_PATH: self.source.get_user_path(),
},
)
def handle_existing_link(
@ -408,74 +355,16 @@ class SourceFlowManager:
class GroupUpdateStage(StageView):
"""Dynamically injected stage which updates the user after enrollment/authentication."""
def get_action(
self, group_id: str, group_properties: dict[str, Any | dict[str, Any]]
) -> tuple[Action, GroupSourceConnection | None]:
"""decide which action should be taken"""
new_connection = self.group_connection_type(source=self.source, identifier=group_id)
existing_connections = self.group_connection_type.objects.filter(
source=self.source, identifier=group_id
)
if existing_connections.exists():
return Action.LINK, existing_connections.first()
# No connection exists, but we match on identifier, so enroll
if self.source.group_matching_mode == SourceGroupMatchingModes.IDENTIFIER:
# We don't save the connection here cause it doesn't have a user assigned yet
return Action.ENROLL, new_connection
# Check for existing groups with matching attributes
query = Q()
if self.source.group_matching_mode in [
SourceGroupMatchingModes.NAME_LINK,
SourceGroupMatchingModes.NAME_DENY,
]:
if not group_properties.get("name", None):
LOGGER.warning(
"Refusing to use none group name", source=self.source, group_id=group_id
)
return Action.DENY, None
query = Q(name__exact=group_properties.get("name"))
LOGGER.debug(
"trying to link with existing group", source=self.source, query=query, group_id=group_id
)
matching_groups = Group.objects.filter(query)
# No matching groups, always enroll
if not matching_groups.exists():
LOGGER.debug(
"no matching groups found, enrolling", source=self.source, group_id=group_id
)
return Action.ENROLL, new_connection
group = matching_groups.first()
if self.source.group_matching_mode in [
SourceGroupMatchingModes.NAME_LINK,
]:
new_connection.group = group
return Action.LINK, new_connection
if self.source.group_matching_mode in [
SourceGroupMatchingModes.NAME_DENY,
]:
LOGGER.info(
"denying source because group exists",
source=self.source,
group=group,
group_id=group_id,
)
return Action.DENY, None
# Should never get here as default enroll case is returned above.
return Action.DENY, None # pragma: no cover
def handle_group(
self, group_id: str, group_properties: dict[str, Any | dict[str, Any]]
) -> Group | None:
action, connection = self.get_action(group_id, group_properties)
action, connection = self.matcher.get_group_action(group_id, group_properties)
if action == Action.ENROLL:
group = Group.objects.create(**group_properties)
connection.group = group
connection.save()
return group
elif action == Action.LINK:
elif action in (Action.LINK, Action.AUTH):
group = connection.group
group.update_attributes(group_properties)
connection.save()
@ -489,6 +378,7 @@ class GroupUpdateStage(StageView):
self.group_connection_type: GroupSourceConnection = (
self.executor.current_stage.group_connection_type
)
self.matcher = SourceMatcher(self.source, None, self.group_connection_type)
raw_groups: dict[str, dict[str, Any | dict[str, Any]]] = self.executor.plan.context[
PLAN_CONTEXT_SOURCE_GROUPS

View File

@ -0,0 +1,152 @@
"""Source user and group matching"""
from dataclasses import dataclass
from enum import Enum
from typing import Any
from django.db.models import Q
from structlog import get_logger
from authentik.core.models import (
Group,
GroupSourceConnection,
Source,
SourceGroupMatchingModes,
SourceUserMatchingModes,
User,
UserSourceConnection,
)
class Action(Enum):
"""Actions that can be decided based on the request and source settings"""
LINK = "link"
AUTH = "auth"
ENROLL = "enroll"
DENY = "deny"
@dataclass
class MatchableProperty:
property: str
link_mode: SourceUserMatchingModes | SourceGroupMatchingModes
deny_mode: SourceUserMatchingModes | SourceGroupMatchingModes
class SourceMatcher:
def __init__(
self,
source: Source,
user_connection_type: type[UserSourceConnection],
group_connection_type: type[GroupSourceConnection],
):
self.source = source
self.user_connection_type = user_connection_type
self.group_connection_type = group_connection_type
self._logger = get_logger().bind(source=self.source)
def get_action(
self,
object_type: type[User | Group],
matchable_properties: list[MatchableProperty],
identifier: str,
properties: dict[str, Any | dict[str, Any]],
) -> tuple[Action, UserSourceConnection | GroupSourceConnection | None]:
connection_type = None
matching_mode = None
identifier_matching_mode = None
if object_type == User:
connection_type = self.user_connection_type
matching_mode = self.source.user_matching_mode
identifier_matching_mode = SourceUserMatchingModes.IDENTIFIER
if object_type == Group:
connection_type = self.group_connection_type
matching_mode = self.source.group_matching_mode
identifier_matching_mode = SourceGroupMatchingModes.IDENTIFIER
if not connection_type or not matching_mode or not identifier_matching_mode:
return Action.DENY, None
new_connection = connection_type(source=self.source, identifier=identifier)
existing_connections = connection_type.objects.filter(
source=self.source, identifier=identifier
)
if existing_connections.exists():
return Action.AUTH, existing_connections.first()
# No connection exists, but we match on identifier, so enroll
if matching_mode == identifier_matching_mode:
# We don't save the connection here cause it doesn't have a user/group assigned yet
return Action.ENROLL, new_connection
# Check for existing users with matching attributes
query = Q()
for matchable_property in matchable_properties:
property = matchable_property.property
if matching_mode in [matchable_property.link_mode, matchable_property.deny_mode]:
if not properties.get(property, None):
self._logger.warning(
"Refusing to use none property", identifier=identifier, property=property
)
return Action.DENY, None
query_args = {
f"{property}__exact": properties[property],
}
query = Q(**query_args)
self._logger.debug(
"Trying to link with existing object", query=query, identifier=identifier
)
matching_objects = object_type.objects.filter(query)
# Not matching objects, always enroll
if not matching_objects.exists():
self._logger.debug("No matching objects found, enrolling")
return Action.ENROLL, new_connection
obj = matching_objects.first()
if matching_mode in [mp.link_mode for mp in matchable_properties]:
attr = None
if object_type == User:
attr = "user"
if object_type == Group:
attr = "group"
setattr(new_connection, attr, obj)
return Action.LINK, new_connection
if matching_mode in [mp.deny_mode for mp in matchable_properties]:
self._logger.info("Denying source because object exists", obj=obj)
return Action.DENY, None
# Should never get here as default enroll case is returned above.
return Action.DENY, None # pragma: no cover
def get_user_action(
self, identifier: str, properties: dict[str, Any | dict[str, Any]]
) -> tuple[Action, UserSourceConnection | None]:
return self.get_action(
User,
[
MatchableProperty(
"username",
SourceUserMatchingModes.USERNAME_LINK,
SourceUserMatchingModes.USERNAME_DENY,
),
MatchableProperty(
"email", SourceUserMatchingModes.EMAIL_LINK, SourceUserMatchingModes.EMAIL_DENY
),
],
identifier,
properties,
)
def get_group_action(
self, identifier: str, properties: dict[str, Any | dict[str, Any]]
) -> tuple[Action, GroupSourceConnection | None]:
return self.get_action(
Group,
[
MatchableProperty(
"name", SourceGroupMatchingModes.NAME_LINK, SourceGroupMatchingModes.NAME_DENY
),
],
identifier,
properties,
)

View File

@ -0,0 +1,59 @@
"""Test Devices API"""
from json import loads
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_admin_user, create_test_user
class TestDevicesAPI(APITestCase):
"""Test applications API"""
def setUp(self) -> None:
self.admin = create_test_admin_user()
self.user1 = create_test_user()
self.device1 = self.user1.staticdevice_set.create()
self.user2 = create_test_user()
self.device2 = self.user2.staticdevice_set.create()
def test_user_api(self):
"""Test user API"""
self.client.force_login(self.user1)
response = self.client.get(
reverse(
"authentik_api:device-list",
)
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(len(body), 1)
self.assertEqual(body[0]["pk"], str(self.device1.pk))
def test_user_api_as_admin(self):
"""Test user API"""
self.client.force_login(self.admin)
response = self.client.get(
reverse(
"authentik_api:device-list",
)
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(len(body), 0)
def test_admin_api(self):
"""Test admin API"""
self.client.force_login(self.admin)
response = self.client.get(
reverse(
"authentik_api:admin-device-list",
)
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(len(body), 2)
self.assertEqual(
{body[0]["pk"], body[1]["pk"]}, {str(self.device1.pk), str(self.device2.pk)}
)

View File

@ -5,7 +5,6 @@ from channels.sessions import CookieMiddleware
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.views.decorators.csrf import ensure_csrf_cookie
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
@ -44,19 +43,19 @@ urlpatterns = [
# Interfaces
path(
"if/admin/",
ensure_csrf_cookie(BrandDefaultRedirectView.as_view(template_name="if/admin.html")),
BrandDefaultRedirectView.as_view(template_name="if/admin.html"),
name="if-admin",
),
path(
"if/user/",
ensure_csrf_cookie(BrandDefaultRedirectView.as_view(template_name="if/user.html")),
BrandDefaultRedirectView.as_view(template_name="if/user.html"),
name="if-user",
),
path(
"if/flow/<slug:flow_slug>/",
# FIXME: move this url to the flows app...also will cause all
# of the reverse calls to be adjusted
ensure_csrf_cookie(FlowInterfaceView.as_view()),
FlowInterfaceView.as_view(),
name="if-flow",
),
# Fallback for WS

View File

@ -3,7 +3,6 @@
from channels.auth import AuthMiddleware
from channels.sessions import CookieMiddleware
from django.urls import path
from django.views.decorators.csrf import ensure_csrf_cookie
from authentik.enterprise.providers.rac.api.connection_tokens import ConnectionTokenViewSet
from authentik.enterprise.providers.rac.api.endpoints import EndpointViewSet
@ -19,12 +18,12 @@ from authentik.root.middleware import ChannelsLoggingMiddleware
urlpatterns = [
path(
"application/rac/<slug:app>/<uuid:endpoint>/",
ensure_csrf_cookie(RACStartView.as_view()),
RACStartView.as_view(),
name="start",
),
path(
"if/rac/<str:token>/",
ensure_csrf_cookie(RACInterface.as_view()),
RACInterface.as_view(),
name="if-rac",
),
]

View File

@ -17,6 +17,7 @@ TENANT_APPS = [
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
"authentik.enterprise.providers.rac",
"authentik.enterprise.stages.authenticator_endpoint_gdtc",
"authentik.enterprise.stages.source",
]

View File

@ -0,0 +1,82 @@
"""AuthenticatorEndpointGDTCStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
)
from authentik.flows.api.stages import StageSerializer
LOGGER = get_logger()
class AuthenticatorEndpointGDTCStageSerializer(EnterpriseRequiredMixin, StageSerializer):
"""AuthenticatorEndpointGDTCStage Serializer"""
class Meta:
model = AuthenticatorEndpointGDTCStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"friendly_name",
"credentials",
]
class AuthenticatorEndpointGDTCStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorEndpointGDTCStage Viewset"""
queryset = AuthenticatorEndpointGDTCStage.objects.all()
serializer_class = AuthenticatorEndpointGDTCStageSerializer
filterset_fields = [
"name",
"configure_flow",
]
search_fields = ["name"]
ordering = ["name"]
class EndpointDeviceSerializer(ModelSerializer):
"""Serializer for Endpoint authenticator devices"""
class Meta:
model = EndpointDevice
fields = ["pk", "name"]
depth = 2
class EndpointDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
UsedByMixin,
GenericViewSet,
):
"""Viewset for Endpoint authenticator devices"""
queryset = EndpointDevice.objects.all()
serializer_class = EndpointDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
class EndpointAdminDeviceViewSet(ModelViewSet):
"""Viewset for Endpoint authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = EndpointDevice.objects.all()
serializer_class = EndpointDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View File

@ -0,0 +1,13 @@
"""authentik Endpoint app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikStageAuthenticatorEndpointConfig(EnterpriseConfig):
"""authentik endpoint config"""
name = "authentik.enterprise.stages.authenticator_endpoint_gdtc"
label = "authentik_stages_authenticator_endpoint_gdtc"
verbose_name = "authentik Enterprise.Stages.Authenticator.Endpoint GDTC"
default = True
mountpoint = "endpoint/gdtc/"

View File

@ -0,0 +1,115 @@
# Generated by Django 5.0.9 on 2024-10-22 11:40
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_flows", "0027_auto_20231028_1424"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AuthenticatorEndpointGDTCStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
("friendly_name", models.TextField(null=True)),
("credentials", models.JSONField()),
(
"configure_flow",
models.ForeignKey(
blank=True,
help_text="Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_flows.flow",
),
),
],
options={
"verbose_name": "Endpoint Authenticator Google Device Trust Connector Stage",
"verbose_name_plural": "Endpoint Authenticator Google Device Trust Connector Stages",
},
bases=("authentik_flows.stage", models.Model),
),
migrations.CreateModel(
name="EndpointDevice",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text="The human-readable name of this device.", max_length=64
),
),
(
"confirmed",
models.BooleanField(default=True, help_text="Is this device ready for use?"),
),
("last_used", models.DateTimeField(null=True)),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
(
"host_identifier",
models.TextField(
help_text="A unique identifier for the endpoint device, usually the device serial number",
unique=True,
),
),
("data", models.JSONField()),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "Endpoint Device",
"verbose_name_plural": "Endpoint Devices",
},
),
migrations.CreateModel(
name="EndpointDeviceConnection",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("attributes", models.JSONField()),
(
"device",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_endpoint_gdtc.endpointdevice",
),
),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage",
),
),
],
),
]

View File

@ -0,0 +1,101 @@
"""Endpoint stage"""
from uuid import uuid4
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
from google.oauth2.service_account import Credentials
from rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.flows.stage import StageView
from authentik.lib.models import SerializerModel
from authentik.stages.authenticator.models import Device
class AuthenticatorEndpointGDTCStage(ConfigurableStage, FriendlyNamedStage, Stage):
"""Setup Google Chrome Device-trust connection"""
credentials = models.JSONField()
def google_credentials(self):
return {
"credentials": Credentials.from_service_account_info(
self.credentials, scopes=["https://www.googleapis.com/auth/verifiedaccess"]
),
}
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
AuthenticatorEndpointGDTCStageSerializer,
)
return AuthenticatorEndpointGDTCStageSerializer
@property
def view(self) -> type[StageView]:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.stage import (
AuthenticatorEndpointStageView,
)
return AuthenticatorEndpointStageView
@property
def component(self) -> str:
return "ak-stage-authenticator-endpoint-gdtc-form"
def ui_user_settings(self) -> UserSettingSerializer | None:
return UserSettingSerializer(
data={
"title": self.friendly_name or str(self._meta.verbose_name),
"component": "ak-user-settings-authenticator-endpoint",
}
)
def __str__(self) -> str:
return f"Endpoint Authenticator Google Device Trust Connector Stage {self.name}"
class Meta:
verbose_name = _("Endpoint Authenticator Google Device Trust Connector Stage")
verbose_name_plural = _("Endpoint Authenticator Google Device Trust Connector Stages")
class EndpointDevice(SerializerModel, Device):
"""Endpoint Device for a single user"""
uuid = models.UUIDField(primary_key=True, default=uuid4)
host_identifier = models.TextField(
unique=True,
help_text="A unique identifier for the endpoint device, usually the device serial number",
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
data = models.JSONField()
@property
def serializer(self) -> Serializer:
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
EndpointDeviceSerializer,
)
return EndpointDeviceSerializer
def __str__(self):
return str(self.name) or str(self.user_id)
class Meta:
verbose_name = _("Endpoint Device")
verbose_name_plural = _("Endpoint Devices")
class EndpointDeviceConnection(models.Model):
device = models.ForeignKey(EndpointDevice, on_delete=models.CASCADE)
stage = models.ForeignKey(AuthenticatorEndpointGDTCStage, on_delete=models.CASCADE)
attributes = models.JSONField()
def __str__(self) -> str:
return f"Endpoint device connection {self.device_id} to {self.stage_id}"

View File

@ -0,0 +1,32 @@
from django.http import HttpResponse
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
FrameChallenge,
FrameChallengeResponse,
)
from authentik.flows.stage import ChallengeStageView
class AuthenticatorEndpointStageView(ChallengeStageView):
"""Endpoint stage"""
response_class = FrameChallengeResponse
def get_challenge(self, *args, **kwargs) -> Challenge:
return FrameChallenge(
data={
"component": "xak-flow-frame",
"url": self.request.build_absolute_uri(
reverse("authentik_stages_authenticator_endpoint_gdtc:chrome")
),
"loading_overlay": True,
"loading_text": _("Verifying your browser..."),
}
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

View File

@ -0,0 +1,9 @@
<html>
<script>
window.parent.postMessage({
message: "submit",
source: "goauthentik.io",
context: "flow-executor"
});
</script>
</html>

View File

@ -0,0 +1,26 @@
"""API URLs"""
from django.urls import path
from authentik.enterprise.stages.authenticator_endpoint_gdtc.api import (
AuthenticatorEndpointGDTCStageViewSet,
EndpointAdminDeviceViewSet,
EndpointDeviceViewSet,
)
from authentik.enterprise.stages.authenticator_endpoint_gdtc.views.dtc import (
GoogleChromeDeviceTrustConnector,
)
urlpatterns = [
path("chrome/", GoogleChromeDeviceTrustConnector.as_view(), name="chrome"),
]
api_urlpatterns = [
("authenticators/endpoint", EndpointDeviceViewSet),
(
"authenticators/admin/endpoint",
EndpointAdminDeviceViewSet,
"admin-endpointdevice",
),
("stages/authenticator/endpoint_gdtc", AuthenticatorEndpointGDTCStageViewSet),
]

View File

@ -0,0 +1,84 @@
from json import dumps, loads
from typing import Any
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.views import View
from googleapiclient.discovery import build
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,
EndpointDevice,
EndpointDeviceConnection,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
# Header we get from chrome that initiates verified access
HEADER_DEVICE_TRUST = "X-Device-Trust"
# Header we send to the client with the challenge
HEADER_ACCESS_CHALLENGE = "X-Verified-Access-Challenge"
# Header we get back from the client that we verify with google
HEADER_ACCESS_CHALLENGE_RESPONSE = "X-Verified-Access-Challenge-Response"
# Header value for x-device-trust that initiates the flow
DEVICE_TRUST_VERIFIED_ACCESS = "VerifiedAccess"
class GoogleChromeDeviceTrustConnector(View):
"""Google Chrome Device-trust connector based endpoint authenticator"""
def get_flow_plan(self) -> FlowPlan:
flow_plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
return flow_plan
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
super().setup(request, *args, **kwargs)
stage: AuthenticatorEndpointGDTCStage = self.get_flow_plan().bindings[0].stage
self.google_client = build(
"verifiedaccess",
"v2",
cache_discovery=False,
**stage.google_credentials(),
)
def get(self, request: HttpRequest) -> HttpResponse:
x_device_trust = request.headers.get(HEADER_DEVICE_TRUST)
x_access_challenge_response = request.headers.get(HEADER_ACCESS_CHALLENGE_RESPONSE)
if x_device_trust == "VerifiedAccess" and x_access_challenge_response is None:
challenge = self.google_client.challenge().generate().execute()
res = HttpResponseRedirect(
self.request.build_absolute_uri(
reverse("authentik_stages_authenticator_endpoint_gdtc:chrome")
)
)
res[HEADER_ACCESS_CHALLENGE] = dumps(challenge)
return res
if x_access_challenge_response:
response = (
self.google_client.challenge()
.verify(body=loads(x_access_challenge_response))
.execute()
)
# Remove deprecated string representation of deviceSignals
response.pop("deviceSignal", None)
flow_plan: FlowPlan = self.get_flow_plan()
device, _ = EndpointDevice.objects.update_or_create(
host_identifier=response["deviceSignals"]["serialNumber"],
user=flow_plan.context.get(PLAN_CONTEXT_PENDING_USER),
defaults={"name": response["deviceSignals"]["hostname"], "data": response},
)
EndpointDeviceConnection.objects.update_or_create(
device=device,
stage=flow_plan.bindings[0].stage,
defaults={
"attributes": response,
},
)
flow_plan.context.setdefault(PLAN_CONTEXT_METHOD, "trusted_endpoint")
flow_plan.context.setdefault(PLAN_CONTEXT_METHOD_ARGS, {})
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS].setdefault("endpoints", [])
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS]["endpoints"].append(response)
request.session[SESSION_KEY_PLAN] = flow_plan
return TemplateResponse(request, "stages/authenticator_endpoint/google_chrome_dtc.html")

View File

@ -1,13 +1,16 @@
"""authentik events signal listener"""
from importlib import import_module
from typing import Any
from django.conf import settings
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.http import HttpRequest
from rest_framework.request import Request
from authentik.core.models import User
from authentik.core.models import AuthenticatedSession, User
from authentik.core.signals import login_failed, password_changed
from authentik.events.apps import SYSTEM_TASK_STATUS
from authentik.events.models import Event, EventAction, SystemTask
@ -23,6 +26,7 @@ from authentik.stages.user_write.signals import user_write
from authentik.tenants.utils import get_current_tenant
SESSION_LOGIN_EVENT = "login_event"
_session_engine = import_module(settings.SESSION_ENGINE)
@receiver(user_logged_in)
@ -43,11 +47,20 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
kwargs[PLAN_CONTEXT_OUTPOST] = flow_plan.context[PLAN_CONTEXT_OUTPOST]
event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user)
request.session[SESSION_LOGIN_EVENT] = event
request.session.save()
def get_login_event(request: HttpRequest) -> Event | None:
def get_login_event(request_or_session: HttpRequest | AuthenticatedSession | None) -> Event | None:
"""Wrapper to get login event that can be mocked in tests"""
return request.session.get(SESSION_LOGIN_EVENT, None)
session = None
if not request_or_session:
return None
if isinstance(request_or_session, HttpRequest | Request):
session = request_or_session.session
if isinstance(request_or_session, AuthenticatedSession):
SessionStore = _session_engine.SessionStore
session = SessionStore(request_or_session.session_key)
return session.get(SESSION_LOGIN_EVENT, None)
@receiver(user_logged_out)

View File

@ -8,7 +8,7 @@ from uuid import UUID
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import JsonResponse
from rest_framework.fields import CharField, ChoiceField, DictField
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField
from rest_framework.request import Request
from authentik.core.api.utils import PassiveSerializer
@ -160,6 +160,20 @@ class AutoSubmitChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-autosubmit")
class FrameChallenge(Challenge):
"""Challenge type to render a frame"""
component = CharField(default="xak-flow-frame")
url = CharField()
loading_overlay = BooleanField(default=False)
loading_text = CharField()
class FrameChallengeResponse(ChallengeResponse):
component = CharField(default="xak-flow-frame")
class DataclassEncoder(DjangoJSONEncoder):
"""Convert any dataclass to json"""

View File

@ -46,6 +46,7 @@ class TestFlowInspector(APITestCase):
res.content,
{
"allow_show_password": False,
"captcha_stage": None,
"component": "ak-stage-identification",
"flow_info": {
"background": flow.background_url,

View File

@ -105,6 +105,10 @@ ldap:
tls:
ciphers: null
sources:
kerberos:
task_timeout_hours: 2
reputation:
expiry: 86400

View File

@ -21,7 +21,14 @@ class DebugSession(Session):
def send(self, req: PreparedRequest, *args, **kwargs):
request_id = str(uuid4())
LOGGER.debug("HTTP request sent", uid=request_id, path=req.path_url, headers=req.headers)
LOGGER.debug(
"HTTP request sent",
uid=request_id,
url=req.url,
method=req.method,
headers=req.headers,
body=req.body,
)
resp = super().send(req, *args, **kwargs)
LOGGER.debug(
"HTTP response received",

View File

@ -108,7 +108,7 @@ class EventMatcherPolicy(Policy):
result=result,
)
matches.append(result)
passing = any(x.passing for x in matches)
passing = all(x.passing for x in matches)
messages = chain(*[x.messages for x in matches])
result = PolicyResult(passing, *messages)
result.source_results = matches

View File

@ -77,11 +77,24 @@ class TestEventMatcherPolicy(TestCase):
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
client_ip="1.2.3.5", app="bar"
client_ip="1.2.3.5", app="foo"
)
response = policy.passes(request)
self.assertFalse(response.passing)
def test_multiple(self):
"""Test multiple"""
event = Event.new(EventAction.LOGIN)
event.app = "foo"
event.client_ip = "1.2.3.4"
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
client_ip="1.2.3.4", app="foo"
)
response = policy.passes(request)
self.assertTrue(response.passing)
def test_invalid(self):
"""Test passing event"""
request = PolicyRequest(get_anonymous_user())

View File

@ -39,6 +39,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"refresh_token_validity",
"include_claims_in_id_token",
"signing_key",
"encryption_key",
"redirect_uris",
"sub_mode",
"property_mappings",

View File

@ -1,6 +1,7 @@
"""id_token utils"""
from dataclasses import asdict, dataclass, field
from hashlib import sha256
from typing import TYPE_CHECKING, Any
from django.db import models
@ -23,8 +24,13 @@ if TYPE_CHECKING:
from authentik.providers.oauth2.models import BaseGrantModel, OAuth2Provider
def hash_session_key(session_key: str) -> str:
"""Hash the session key for inclusion in JWTs as `sid`"""
return sha256(session_key.encode("ascii")).hexdigest()
class SubModes(models.TextChoices):
"""Mode after which 'sub' attribute is generateed, for compatibility reasons"""
"""Mode after which 'sub' attribute is generated, for compatibility reasons"""
HASHED_USER_ID = "hashed_user_id", _("Based on the Hashed User ID")
USER_ID = "user_id", _("Based on user ID")
@ -51,7 +57,8 @@ class IDToken:
and potentially other requested Claims. The ID Token is represented as a
JSON Web Token (JWT) [JWT].
https://openid.net/specs/openid-connect-core-1_0.html#IDToken"""
https://openid.net/specs/openid-connect-core-1_0.html#IDToken
https://www.iana.org/assignments/jwt/jwt.xhtml"""
# Issuer, https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.1
iss: str | None = None
@ -79,6 +86,8 @@ class IDToken:
nonce: str | None = None
# Access Token hash value, http://openid.net/specs/openid-connect-core-1_0.html
at_hash: str | None = None
# Session ID, https://openid.net/specs/openid-connect-frontchannel-1_0.html#ClaimsContents
sid: str | None = None
claims: dict[str, Any] = field(default_factory=dict)
@ -116,9 +125,11 @@ class IDToken:
now = timezone.now()
id_token.iat = int(now.timestamp())
id_token.auth_time = int(token.auth_time.timestamp())
if token.session:
id_token.sid = hash_session_key(token.session.session_key)
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
auth_event = get_login_event(request)
auth_event = get_login_event(token.session)
if auth_event:
# Also check which method was used for authentication
method = auth_event.context.get(PLAN_CONTEXT_METHOD, "")

View File

@ -3,6 +3,7 @@
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.lib.utils.time
@ -14,7 +15,7 @@ scope_uid_map = {
}
def set_managed_flag(apps: Apps, schema_editor):
def set_managed_flag(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
db_alias = schema_editor.connection.alias
for mapping in ScopeMapping.objects.using(db_alias).filter(name__startswith="Autogenerated "):

View File

@ -0,0 +1,42 @@
# Generated by Django 5.0.9 on 2024-10-16 14:53
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0004_alter_certificatekeypair_name"),
(
"authentik_providers_oauth2",
"0020_remove_accesstoken_authentik_p_token_4bc870_idx_and_more",
),
]
operations = [
migrations.AddField(
model_name="oauth2provider",
name="encryption_key",
field=models.ForeignKey(
help_text="Key used to encrypt the tokens. When set, tokens will be encrypted and returned as JWEs.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="oauth2provider_encryption_key_set",
to="authentik_crypto.certificatekeypair",
verbose_name="Encryption Key",
),
),
migrations.AlterField(
model_name="oauth2provider",
name="signing_key",
field=models.ForeignKey(
help_text="Key used to sign the tokens.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="oauth2provider_signing_key_set",
to="authentik_crypto.certificatekeypair",
verbose_name="Signing Key",
),
),
]

View File

@ -0,0 +1,113 @@
# Generated by Django 5.0.9 on 2024-10-23 13:38
from hashlib import sha256
import django.db.models.deletion
from django.db import migrations, models
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.lib.migrations import progress_bar
def migrate_session(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
AuthenticatedSession = apps.get_model("authentik_core", "authenticatedsession")
AuthorizationCode = apps.get_model("authentik_providers_oauth2", "authorizationcode")
AccessToken = apps.get_model("authentik_providers_oauth2", "accesstoken")
RefreshToken = apps.get_model("authentik_providers_oauth2", "refreshtoken")
db_alias = schema_editor.connection.alias
print(f"\nFetching session keys, this might take a couple of minutes...")
session_ids = {}
for session in progress_bar(AuthenticatedSession.objects.using(db_alias).all()):
session_ids[sha256(session.session_key.encode("ascii")).hexdigest()] = session.session_key
for model in [AuthorizationCode, AccessToken, RefreshToken]:
print(
f"\nAdding session to {model._meta.verbose_name}, this might take a couple of minutes..."
)
for code in progress_bar(model.objects.using(db_alias).all()):
if code.session_id_old not in session_ids:
continue
code.session = (
AuthenticatedSession.objects.using(db_alias)
.filter(session_key=session_ids[code.session_id_old])
.first()
)
code.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0040_provider_invalidation_flow"),
("authentik_providers_oauth2", "0021_oauth2provider_encryption_key_and_more"),
]
operations = [
migrations.RenameField(
model_name="accesstoken",
old_name="session_id",
new_name="session_id_old",
),
migrations.RenameField(
model_name="authorizationcode",
old_name="session_id",
new_name="session_id_old",
),
migrations.RenameField(
model_name="refreshtoken",
old_name="session_id",
new_name="session_id_old",
),
migrations.AddField(
model_name="accesstoken",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.authenticatedsession",
),
),
migrations.AddField(
model_name="authorizationcode",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.authenticatedsession",
),
),
migrations.AddField(
model_name="devicetoken",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.authenticatedsession",
),
),
migrations.AddField(
model_name="refreshtoken",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.authenticatedsession",
),
),
migrations.RunPython(migrate_session),
migrations.RemoveField(
model_name="accesstoken",
name="session_id_old",
),
migrations.RemoveField(
model_name="authorizationcode",
name="session_id_old",
),
migrations.RemoveField(
model_name="refreshtoken",
name="session_id_old",
),
]

View File

@ -18,12 +18,21 @@ from django.http import HttpRequest
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from jwcrypto.common import json_encode
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK
from jwt import encode
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.brands.models import WebfingerProvider
from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.core.models import (
AuthenticatedSession,
ExpiringModel,
PropertyMapping,
Provider,
User,
)
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
from authentik.lib.models import SerializerModel
@ -206,9 +215,19 @@ class OAuth2Provider(WebfingerProvider, Provider):
verbose_name=_("Signing Key"),
on_delete=models.SET_NULL,
null=True,
help_text=_("Key used to sign the tokens."),
related_name="oauth2provider_signing_key_set",
)
encryption_key = models.ForeignKey(
CertificateKeyPair,
verbose_name=_("Encryption Key"),
on_delete=models.SET_NULL,
null=True,
help_text=_(
"Key used to sign the tokens. Only required when JWT Algorithm is set to RS256."
"Key used to encrypt the tokens. When set, "
"tokens will be encrypted and returned as JWEs."
),
related_name="oauth2provider_encryption_key_set",
)
jwks_sources = models.ManyToManyField(
@ -287,7 +306,27 @@ class OAuth2Provider(WebfingerProvider, Provider):
if self.signing_key:
headers["kid"] = self.signing_key.kid
key, alg = self.jwt_key
return encode(payload, key, algorithm=alg, headers=headers)
encoded = encode(payload, key, algorithm=alg, headers=headers)
if self.encryption_key:
return self.encrypt(encoded)
return encoded
def encrypt(self, raw: str) -> str:
"""Encrypt JWT"""
key = JWK.from_pem(self.encryption_key.certificate_data.encode())
jwe = JWE(
raw,
json_encode(
{
"alg": "RSA-OAEP-256",
"enc": "A256CBC-HS512",
"typ": "JWE",
"kid": self.encryption_key.kid,
}
),
)
jwe.add_recipient(key)
return jwe.serialize(compact=True)
def webfinger(self, resource: str, request: HttpRequest):
return {
@ -320,7 +359,9 @@ class BaseGrantModel(models.Model):
revoked = models.BooleanField(default=False)
_scope = models.TextField(default="", verbose_name=_("Scopes"))
auth_time = models.DateTimeField(verbose_name="Authentication time")
session_id = models.CharField(default="", blank=True)
session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
)
class Meta:
abstract = True
@ -458,6 +499,9 @@ class DeviceToken(ExpiringModel):
device_code = models.TextField(default=generate_key)
user_code = models.TextField(default=generate_code_fixed_length)
_scope = models.TextField(default="", verbose_name=_("Scopes"))
session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
)
@property
def scope(self) -> list[str]:

View File

@ -1,5 +1,3 @@
from hashlib import sha256
from django.contrib.auth.signals import user_logged_out
from django.dispatch import receiver
from django.http import HttpRequest
@ -13,5 +11,4 @@ def user_logged_out_oauth_access_token(sender, request: HttpRequest, user: User,
"""Revoke access tokens upon user logout"""
if not request.session or not request.session.session_key:
return
hashed_session_key = sha256(request.session.session_key.encode("ascii")).hexdigest()
AccessToken.objects.filter(user=user, session_id=hashed_session_key).delete()
AccessToken.objects.filter(user=user, session__session_key=request.session.session_key).delete()

View File

@ -412,6 +412,73 @@ class TestAuthorize(OAuthTestCase):
delta=5,
)
@apply_blueprint("system/providers-oauth2.yaml")
def test_full_implicit_enc(self):
"""Test full authorization with encryption"""
flow = create_test_flow()
provider: OAuth2Provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="http://localhost",
signing_key=self.keypair,
encryption_key=self.keypair,
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
managed__in=[
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-profile",
]
)
)
provider.property_mappings.add(
ScopeMapping.objects.create(
name=generate_id(), scope_name="test", expression="""return {"sub": "foo"}"""
)
)
Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
with patch(
"authentik.providers.oauth2.id_token.get_login_event",
MagicMock(
return_value=Event(
action=EventAction.LOGIN,
context={PLAN_CONTEXT_METHOD: "password"},
created=now(),
)
),
):
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
"client_id": "test",
"state": state,
"scope": "openid test",
"redirect_uri": "http://localhost",
"nonce": generate_id(),
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
token: AccessToken = AccessToken.objects.filter(user=user).first()
expires = timedelta_from_string(provider.access_token_validity).total_seconds()
jwt = self.validate_jwe(token, provider)
self.assertEqual(jwt["amr"], ["pwd"])
self.assertEqual(jwt["sub"], "foo")
self.assertAlmostEqual(
jwt["exp"] - now().timestamp(),
expires,
delta=5,
)
def test_full_fragment_code(self):
"""Test full authorization"""
flow = create_test_flow()

View File

@ -93,6 +93,24 @@ class TestJWKS(OAuthTestCase):
self.assertEqual(len(body["keys"]), 1)
PyJWKSet.from_dict(body)
def test_enc(self):
"""Test with JWE"""
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=create_test_cert(PrivateKeyAlg.ECDSA),
encryption_key=create_test_cert(PrivateKeyAlg.ECDSA),
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})
)
body = json.loads(response.content.decode())
self.assertEqual(len(body["keys"]), 2)
PyJWKSet.from_dict(body)
def test_ecdsa_coords_mismatched(self):
"""Test JWKS request with ES256"""
cert = CertificateKeyPair.objects.create(

View File

@ -152,6 +152,36 @@ class TestToken(OAuthTestCase):
)
self.validate_jwt(access, provider)
def test_auth_code_enc(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=self.keypair,
encryption_key=self.keypair,
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
self.app.save()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
code = AuthorizationCode.objects.create(
code="foobar", provider=provider, user=user, auth_time=timezone.now()
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertEqual(response.status_code, 200)
access: AccessToken = AccessToken.objects.filter(user=user, provider=provider).first()
self.validate_jwe(access, provider)
@apply_blueprint("system/providers-oauth2.yaml")
def test_refresh_token_view(self):
"""test request param"""

View File

@ -34,7 +34,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
self.factory = RequestFactory()
self.cert = create_test_cert()
jwk = JWKSView().get_jwk_for_key(self.cert)
jwk = JWKSView().get_jwk_for_key(self.cert, "sig")
self.source: OAuthSource = OAuthSource.objects.create(
name=generate_id(),
slug=generate_id(),

View File

@ -3,6 +3,8 @@
from typing import Any
from django.test import TestCase
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK
from jwt import decode
from authentik.core.tests.utils import create_test_cert
@ -32,6 +34,15 @@ class OAuthTestCase(TestCase):
if key in container:
self.assertIsNotNone(container[key])
def validate_jwe(self, token: AccessToken, provider: OAuth2Provider) -> dict[str, Any]:
"""Validate JWEs"""
private_key = JWK.from_pem(provider.encryption_key.key_data.encode())
jwetoken = JWE()
jwetoken.deserialize(token.token, key=private_key)
token.token = jwetoken.payload.decode()
return self.validate_jwt(token, provider)
def validate_jwt(self, token: AccessToken, provider: OAuth2Provider) -> dict[str, Any]:
"""Validate that all required fields are set"""
key, alg = provider.jwt_key

View File

@ -2,7 +2,6 @@
from dataclasses import InitVar, dataclass, field
from datetime import timedelta
from hashlib import sha256
from json import dumps
from re import error as RegexError
from re import fullmatch
@ -16,7 +15,7 @@ from django.utils import timezone
from django.utils.translation import gettext as _
from structlog.stdlib import get_logger
from authentik.core.models import Application
from authentik.core.models import Application, AuthenticatedSession
from authentik.events.models import Event, EventAction
from authentik.events.signals import get_login_event
from authentik.flows.challenge import (
@ -318,7 +317,9 @@ class OAuthAuthorizationParams:
expires=now + timedelta_from_string(self.provider.access_code_validity),
scope=self.scope,
nonce=self.nonce,
session_id=sha256(request.session.session_key.encode("ascii")).hexdigest(),
session=AuthenticatedSession.objects.filter(
session_key=request.session.session_key
).first(),
)
if self.code_challenge and self.code_challenge_method:
@ -610,7 +611,9 @@ class OAuthFulfillmentStage(StageView):
expires=access_token_expiry,
provider=self.provider,
auth_time=auth_event.created if auth_event else now,
session_id=sha256(self.request.session.session_key.encode("ascii")).hexdigest(),
session=AuthenticatedSession.objects.filter(
session_key=self.request.session.session_key
).first(),
)
id_token = IDToken.new(self.provider, token, self.request)

View File

@ -64,36 +64,42 @@ def to_base64url_uint(val: int, min_length: int = 0) -> bytes:
class JWKSView(View):
"""Show RSA Key data for Provider"""
def get_jwk_for_key(self, key: CertificateKeyPair) -> dict | None:
def get_jwk_for_key(self, key: CertificateKeyPair, use: str) -> dict | None:
"""Convert a certificate-key pair into JWK"""
private_key = key.private_key
key_data = None
if not private_key:
return key_data
key_data = {}
if use == "sig":
if isinstance(private_key, RSAPrivateKey):
key_data["alg"] = JWTAlgorithms.RS256
elif isinstance(private_key, EllipticCurvePrivateKey):
key_data["alg"] = JWTAlgorithms.ES256
elif use == "enc":
key_data["alg"] = "RSA-OAEP-256"
key_data["enc"] = "A256CBC-HS512"
if isinstance(private_key, RSAPrivateKey):
public_key: RSAPublicKey = private_key.public_key()
public_numbers = public_key.public_numbers()
key_data = {
"kid": key.kid,
"kty": "RSA",
"alg": JWTAlgorithms.RS256,
"use": "sig",
"n": to_base64url_uint(public_numbers.n).decode(),
"e": to_base64url_uint(public_numbers.e).decode(),
}
key_data["kid"] = key.kid
key_data["kty"] = "RSA"
key_data["use"] = use
key_data["n"] = to_base64url_uint(public_numbers.n).decode()
key_data["e"] = to_base64url_uint(public_numbers.e).decode()
elif isinstance(private_key, EllipticCurvePrivateKey):
public_key: EllipticCurvePublicKey = private_key.public_key()
public_numbers = public_key.public_numbers()
curve_type = type(public_key.curve)
key_data = {
"kid": key.kid,
"kty": "EC",
"alg": JWTAlgorithms.ES256,
"use": "sig",
"x": to_base64url_uint(public_numbers.x, min_length_map[curve_type]).decode(),
"y": to_base64url_uint(public_numbers.y, min_length_map[curve_type]).decode(),
"crv": ec_crv_map.get(curve_type, public_key.curve.name),
}
key_data["kid"] = key.kid
key_data["kty"] = "EC"
key_data["use"] = use
key_data["x"] = to_base64url_uint(public_numbers.x, min_length_map[curve_type]).decode()
key_data["y"] = to_base64url_uint(public_numbers.y, min_length_map[curve_type]).decode()
key_data["crv"] = ec_crv_map.get(curve_type, public_key.curve.name)
else:
return key_data
key_data["x5c"] = [b64encode(key.certificate.public_bytes(Encoding.DER)).decode("utf-8")]
@ -113,14 +119,19 @@ class JWKSView(View):
"""Show JWK Key data for Provider"""
application = get_object_or_404(Application, slug=application_slug)
provider: OAuth2Provider = get_object_or_404(OAuth2Provider, pk=application.provider_id)
signing_key: CertificateKeyPair = provider.signing_key
response_data = {}
if signing_key:
jwk = self.get_jwk_for_key(signing_key)
if signing_key := provider.signing_key:
jwk = self.get_jwk_for_key(signing_key, "sig")
if jwk:
response_data["keys"] = [jwk]
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)
if encryption_key := provider.encryption_key:
jwk = self.get_jwk_for_key(encryption_key, "enc")
if jwk:
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)
response = JsonResponse(response_data)
response["Access-Control-Allow-Origin"] = "*"

View File

@ -46,7 +46,7 @@ class ProviderInfoView(View):
if SCOPE_OPENID not in scopes:
scopes.append(SCOPE_OPENID)
_, supported_alg = provider.jwt_key
return {
config = {
"issuer": provider.get_issuer(self.request),
"authorization_endpoint": self.request.build_absolute_uri(
reverse("authentik_providers_oauth2:authorize")
@ -114,6 +114,10 @@ class ProviderInfoView(View):
"claims_parameter_supported": False,
"code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
}
if provider.encryption_key:
config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
return config
def get_claims(self, provider: OAuth2Provider) -> list[str]:
"""Get a list of supported claims based on configured scope mappings"""

View File

@ -439,15 +439,14 @@ class TokenParams:
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",
"path": f"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}",
"type": UserTypes.SERVICE_ACCOUNT,
},
)
self.user.attributes[USER_ATTRIBUTE_GENERATED] = True
self.user.save()
self.__check_policy_access(app, request)
Event.new(
@ -471,9 +470,6 @@ class TokenParams:
self.user, created = User.objects.update_or_create(
username=f"{self.provider.name}-{token.get('sub')}",
defaults={
"attributes": {
USER_ATTRIBUTE_GENERATED: True,
},
"last_login": timezone.now(),
"name": (
f"Autogenerated user from application {app.name} (client credentials JWT)"
@ -482,6 +478,8 @@ class TokenParams:
"type": UserTypes.SERVICE_ACCOUNT,
},
)
self.user.attributes[USER_ATTRIBUTE_GENERATED] = True
self.user.save()
exp = token.get("exp")
if created and exp:
self.user.attributes[USER_ATTRIBUTE_EXPIRES] = exp
@ -552,7 +550,7 @@ class TokenView(View):
# Keep same scopes as previous token
scope=self.params.authorization_code.scope,
auth_time=self.params.authorization_code.auth_time,
session_id=self.params.authorization_code.session_id,
session=self.params.authorization_code.session,
)
access_id_token = IDToken.new(
self.provider,
@ -580,7 +578,7 @@ class TokenView(View):
expires=refresh_token_expiry,
provider=self.provider,
auth_time=self.params.authorization_code.auth_time,
session_id=self.params.authorization_code.session_id,
session=self.params.authorization_code.session,
)
id_token = IDToken.new(
self.provider,
@ -613,7 +611,7 @@ class TokenView(View):
# Keep same scopes as previous token
scope=self.params.refresh_token.scope,
auth_time=self.params.refresh_token.auth_time,
session_id=self.params.refresh_token.session_id,
session=self.params.refresh_token.session,
)
access_token.id_token = IDToken.new(
self.provider,
@ -629,7 +627,7 @@ class TokenView(View):
expires=refresh_token_expiry,
provider=self.provider,
auth_time=self.params.refresh_token.auth_time,
session_id=self.params.refresh_token.session_id,
session=self.params.refresh_token.session,
)
id_token = IDToken.new(
self.provider,
@ -687,13 +685,14 @@ class TokenView(View):
raise DeviceCodeError("authorization_pending")
now = timezone.now()
access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity)
auth_event = get_login_event(self.request)
auth_event = get_login_event(self.params.device_code.session)
access_token = AccessToken(
provider=self.provider,
user=self.params.device_code.user,
expires=access_token_expiry,
scope=self.params.device_code.scope,
auth_time=auth_event.created if auth_event else now,
session=self.params.device_code.session,
)
access_token.id_token = IDToken.new(
self.provider,

View File

@ -1,13 +1,12 @@
"""proxy provider tasks"""
from hashlib import sha256
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.db import DatabaseError, InternalError, ProgrammingError
from authentik.outposts.consumer import OUTPOST_GROUP
from authentik.outposts.models import Outpost, OutpostType
from authentik.providers.oauth2.id_token import hash_session_key
from authentik.providers.proxy.models import ProxyProvider
from authentik.root.celery import CELERY_APP
@ -26,7 +25,7 @@ def proxy_set_defaults():
def proxy_on_logout(session_id: str):
"""Update outpost instances connected to a single outpost"""
layer = get_channel_layer()
hashed_session_id = sha256(session_id.encode("ascii")).hexdigest()
hashed_session_id = hash_session_key(session_id)
for outpost in Outpost.objects.filter(type=OutpostType.PROXY):
group = OUTPOST_GROUP % {"outpost_pk": str(outpost.pk)}
async_to_sync(layer.group_send)(

View File

@ -50,6 +50,7 @@ class AssertionProcessor:
_issue_instant: str
_assertion_id: str
_response_id: str
_valid_not_before: str
_session_not_on_or_after: str
@ -62,6 +63,7 @@ class AssertionProcessor:
self._issue_instant = get_time_string()
self._assertion_id = get_random_id()
self._response_id = get_random_id()
self._valid_not_before = get_time_string(
timedelta_from_string(self.provider.assertion_valid_not_before)
@ -130,7 +132,9 @@ class AssertionProcessor:
"""Generate AuthnStatement with AuthnContext and ContextClassRef Elements."""
auth_n_statement = Element(f"{{{NS_SAML_ASSERTION}}}AuthnStatement")
auth_n_statement.attrib["AuthnInstant"] = self._valid_not_before
auth_n_statement.attrib["SessionIndex"] = self._assertion_id
auth_n_statement.attrib["SessionIndex"] = sha256(
self.http_request.session.session_key.encode("ascii")
).hexdigest()
auth_n_statement.attrib["SessionNotOnOrAfter"] = self._session_not_on_or_after
auth_n_context = SubElement(auth_n_statement, f"{{{NS_SAML_ASSERTION}}}AuthnContext")
@ -285,7 +289,7 @@ class AssertionProcessor:
response.attrib["Version"] = "2.0"
response.attrib["IssueInstant"] = self._issue_instant
response.attrib["Destination"] = self.provider.acs_url
response.attrib["ID"] = get_random_id()
response.attrib["ID"] = self._response_id
if self.auth_n_request.id:
response.attrib["InResponseTo"] = self.auth_n_request.id
@ -308,7 +312,7 @@ class AssertionProcessor:
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + self._assertion_id,
uri="#" + element.attrib["ID"],
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)

View File

@ -180,6 +180,10 @@ class TestAuthNRequest(TestCase):
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Ensure both response and assertion ID are in the response twice (once as ID attribute,
# once as ds:Reference URI)
self.assertEqual(response.count(response_proc._assertion_id), 2)
self.assertEqual(response.count(response_proc._response_id), 2)
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)

View File

@ -2,9 +2,10 @@
from itertools import batched
from django.db import transaction
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from pydanticscim.responses import PatchOp, PatchOperation
from pydanticscim.responses import PatchOp
from authentik.core.models import Group
from authentik.lib.sync.mapper import PropertyMappingManager
@ -19,7 +20,7 @@ from authentik.providers.scim.clients.base import SCIMClient
from authentik.providers.scim.clients.exceptions import (
SCIMRequestException,
)
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchRequest
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOperation, PatchRequest
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
from authentik.providers.scim.models import (
SCIMMapping,
@ -104,13 +105,47 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
provider=self.provider, group=group, scim_id=scim_id
)
users = list(group.users.order_by("id").values_list("id", flat=True))
self._patch_add_users(group, users)
self._patch_add_users(connection, users)
return connection
def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group"""
scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id
try:
if self._config.patch.supported:
return self._update_patch(group, scim_group, connection)
return self._update_put(group, scim_group, connection)
except NotFoundSyncException:
# Resource missing is handled by self.write, which will re-create the group
raise
def _update_patch(
self, group: Group, scim_group: SCIMGroupSchema, connection: SCIMProviderGroup
):
"""Update a group via PATCH request"""
# Patch group's attributes instead of replacing it and re-adding users if we can
self._request(
"PATCH",
f"/Groups/{connection.scim_id}",
json=PatchRequest(
Operations=[
PatchOperation(
op=PatchOp.replace,
path=None,
value=scim_group.model_dump(mode="json", exclude_unset=True),
)
]
).model_dump(
mode="json",
exclude_unset=True,
exclude_none=True,
),
)
return self.patch_compare_users(group)
def _update_put(self, group: Group, scim_group: SCIMGroupSchema, connection: SCIMProviderGroup):
"""Update a group via PUT request"""
try:
self._request(
"PUT",
@ -120,33 +155,25 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
exclude_unset=True,
),
)
users = list(group.users.order_by("id").values_list("id", flat=True))
return self._patch_add_users(group, users)
except NotFoundSyncException:
# Resource missing is handled by self.write, which will re-create the group
raise
return self.patch_compare_users(group)
except (SCIMRequestException, ObjectExistsSyncException):
# Some providers don't support PUT on groups, so this is mainly a fix for the initial
# sync, send patch add requests for all the users the group currently has
users = list(group.users.order_by("id").values_list("id", flat=True))
self._patch_add_users(group, users)
# Also update the group name
return self._patch(
scim_group.id,
PatchOperation(
op=PatchOp.replace,
path="displayName",
value=scim_group.displayName,
),
)
return self._update_patch(group, scim_group, connection)
def update_group(self, group: Group, action: Direction, users_set: set[int]):
"""Update a group, either using PUT to replace it or PATCH if supported"""
scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=group).first()
if not scim_group:
self.logger.warning(
"could not sync group membership, group does not exist", group=group
)
return
if self._config.patch.supported:
if action == Direction.add:
return self._patch_add_users(group, users_set)
return self._patch_add_users(scim_group, users_set)
if action == Direction.remove:
return self._patch_remove_users(group, users_set)
return self._patch_remove_users(scim_group, users_set)
try:
return self.write(group)
except SCIMRequestException as exc:
@ -154,19 +181,24 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
# Assume that provider does not support PUT and also doesn't support
# ServiceProviderConfig, so try PATCH as a fallback
if action == Direction.add:
return self._patch_add_users(group, users_set)
return self._patch_add_users(scim_group, users_set)
if action == Direction.remove:
return self._patch_remove_users(group, users_set)
return self._patch_remove_users(scim_group, users_set)
raise exc
def _patch(
def _patch_chunked(
self,
group_id: str,
*ops: PatchOperation,
):
"""Helper function that chunks patch requests based on the maxOperations attribute.
This is not strictly according to specs but there's nothing in the schema that allows the
us to know what the maximum patch operations per request should be."""
chunk_size = self._config.bulk.maxOperations
if chunk_size < 1:
chunk_size = len(ops)
if len(ops) < 1:
return
for chunk in batched(ops, chunk_size):
req = PatchRequest(Operations=list(chunk))
self._request(
@ -177,16 +209,70 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
),
)
def _patch_add_users(self, group: Group, users_set: set[int]):
"""Add users in users_set to group"""
if len(users_set) < 1:
return
@transaction.atomic
def patch_compare_users(self, group: Group):
"""Compare users with a SCIM group and add/remove any differences"""
# Get scim group first
scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=group).first()
if not scim_group:
self.logger.warning(
"could not sync group membership, group does not exist", group=group
)
return
# Get a list of all users in the authentik group
raw_users_should = list(group.users.order_by("id").values_list("id", flat=True))
# Lookup the SCIM IDs of the users
users_should: list[str] = list(
SCIMProviderUser.objects.filter(
user__pk__in=raw_users_should, provider=self.provider
).values_list("scim_id", flat=True)
)
if len(raw_users_should) != len(users_should):
self.logger.warning(
"User count mismatch, not all users in the group are synced to SCIM yet.",
group=group,
)
# Get current group status
current_group = SCIMGroupSchema.model_validate(
self._request("GET", f"/Groups/{scim_group.scim_id}")
)
users_to_add = []
users_to_remove = []
# Check users currently in group and if they shouldn't be in the group and remove them
for user in current_group.members or []:
if user.value not in users_should:
users_to_remove.append(user.value)
# Check users that should be in the group and add them
for user in users_should:
if len([x for x in current_group.members if x.value == user]) < 1:
users_to_add.append(user)
# Only send request if we need to make changes
if len(users_to_add) < 1 and len(users_to_remove) < 1:
return
return self._patch_chunked(
scim_group.scim_id,
*[
PatchOperation(
op=PatchOp.add,
path="members",
value=[{"value": x}],
)
for x in users_to_add
],
*[
PatchOperation(
op=PatchOp.remove,
path="members",
value=[{"value": x}],
)
for x in users_to_remove
],
)
def _patch_add_users(self, scim_group: SCIMProviderGroup, users_set: set[int]):
"""Add users in users_set to group"""
if len(users_set) < 1:
return
user_ids = list(
SCIMProviderUser.objects.filter(
user__pk__in=users_set, provider=self.provider
@ -194,7 +280,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
)
if len(user_ids) < 1:
return
self._patch(
self._patch_chunked(
scim_group.scim_id,
*[
PatchOperation(
@ -206,16 +292,10 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
],
)
def _patch_remove_users(self, group: Group, users_set: set[int]):
def _patch_remove_users(self, scim_group: SCIMProviderGroup, users_set: set[int]):
"""Remove users in users_set from group"""
if len(users_set) < 1:
return
scim_group = SCIMProviderGroup.objects.filter(provider=self.provider, group=group).first()
if not scim_group:
self.logger.warning(
"could not sync group membership, group does not exist", group=group
)
return
user_ids = list(
SCIMProviderUser.objects.filter(
user__pk__in=users_set, provider=self.provider
@ -223,7 +303,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
)
if len(user_ids) < 1:
return
self._patch(
self._patch_chunked(
scim_group.scim_id,
*[
PatchOperation(

View File

@ -2,6 +2,7 @@
from pydantic import Field
from pydanticscim.group import Group as BaseGroup
from pydanticscim.responses import PatchOperation as BasePatchOperation
from pydanticscim.responses import PatchRequest as BasePatchRequest
from pydanticscim.responses import SCIMError as BaseSCIMError
from pydanticscim.service_provider import Bulk as BaseBulk
@ -68,6 +69,12 @@ class PatchRequest(BasePatchRequest):
schemas: tuple[str] = ("urn:ietf:params:scim:api:messages:2.0:PatchOp",)
class PatchOperation(BasePatchOperation):
"""PatchOperation with optional path"""
path: str | None
class SCIMError(BaseSCIMError):
"""SCIM error with optional status code"""

View File

@ -252,3 +252,118 @@ class SCIMMembershipTests(TestCase):
],
},
)
def test_member_add_save(self):
"""Test member add + save"""
config = ServiceProviderConfiguration.default()
config.patch.supported = True
user_scim_id = generate_id()
group_scim_id = generate_id()
uid = generate_id()
group = Group.objects.create(
name=uid,
)
user = User.objects.create(username=generate_id())
# Test initial sync of group creation
with Mocker() as mocker:
mocker.get(
"https://localhost/ServiceProviderConfig",
json=config.model_dump(),
)
mocker.post(
"https://localhost/Users",
json={
"id": user_scim_id,
},
)
mocker.post(
"https://localhost/Groups",
json={
"id": group_scim_id,
},
)
self.configure()
sync_tasks.trigger_single_task(self.provider, scim_sync).get()
self.assertEqual(mocker.call_count, 6)
self.assertEqual(mocker.request_history[0].method, "GET")
self.assertEqual(mocker.request_history[1].method, "GET")
self.assertEqual(mocker.request_history[2].method, "GET")
self.assertEqual(mocker.request_history[3].method, "POST")
self.assertEqual(mocker.request_history[4].method, "GET")
self.assertEqual(mocker.request_history[5].method, "POST")
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": ""},
"displayName": "",
"userName": user.username,
},
)
self.assertJSONEqual(
mocker.request_history[5].body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
with Mocker() as mocker:
mocker.get(
"https://localhost/ServiceProviderConfig",
json=config.model_dump(),
)
mocker.get(
f"https://localhost/Groups/{group_scim_id}",
json={},
)
mocker.patch(
f"https://localhost/Groups/{group_scim_id}",
json={},
)
group.users.add(user)
group.save()
self.assertEqual(mocker.call_count, 5)
self.assertEqual(mocker.request_history[0].method, "GET")
self.assertEqual(mocker.request_history[1].method, "PATCH")
self.assertEqual(mocker.request_history[2].method, "GET")
self.assertEqual(mocker.request_history[3].method, "PATCH")
self.assertEqual(mocker.request_history[4].method, "GET")
self.assertJSONEqual(
mocker.request_history[1].body,
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "add",
"path": "members",
"value": [{"value": user_scim_id}],
}
],
},
)
self.assertJSONEqual(
mocker.request_history[3].body,
{
"Operations": [
{
"op": "replace",
"value": {
"id": group_scim_id,
"displayName": group.name,
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
},
}
]
},
)

View File

@ -41,7 +41,9 @@ class SessionMiddleware(UpstreamSessionMiddleware):
# Since go does not consider localhost with http a secure origin
# we can't set the secure flag.
user_agent = request.META.get("HTTP_USER_AGENT", "")
if user_agent.startswith("goauthentik.io/outpost/") or "safari" in user_agent.lower():
if user_agent.startswith("goauthentik.io/outpost/") or (
"safari" in user_agent.lower() and "chrome" not in user_agent.lower()
):
return False
return True
return False

View File

@ -38,6 +38,7 @@ LANGUAGE_COOKIE_NAME = "authentik_language"
SESSION_COOKIE_NAME = "authentik_session"
SESSION_COOKIE_DOMAIN = CONFIG.get("cookie_domain", None)
APPEND_SLASH = False
X_FRAME_OPTIONS = "SAMEORIGIN"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
@ -90,6 +91,7 @@ TENANT_APPS = [
"authentik.providers.scim",
"authentik.rbac",
"authentik.recovery",
"authentik.sources.kerberos",
"authentik.sources.ldap",
"authentik.sources.oauth",
"authentik.sources.plex",

View File

View File

@ -0,0 +1,31 @@
"""Kerberos Property Mapping API"""
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.property_mappings import PropertyMappingFilterSet, PropertyMappingSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.kerberos.models import KerberosSourcePropertyMapping
class KerberosSourcePropertyMappingSerializer(PropertyMappingSerializer):
"""Kerberos PropertyMapping Serializer"""
class Meta(PropertyMappingSerializer.Meta):
model = KerberosSourcePropertyMapping
class KerberosSourcePropertyMappingFilter(PropertyMappingFilterSet):
"""Filter for KerberosSourcePropertyMapping"""
class Meta(PropertyMappingFilterSet.Meta):
model = KerberosSourcePropertyMapping
class KerberosSourcePropertyMappingViewSet(UsedByMixin, ModelViewSet):
"""KerberosSource PropertyMapping Viewset"""
queryset = KerberosSourcePropertyMapping.objects.all()
serializer_class = KerberosSourcePropertyMappingSerializer
filterset_class = KerberosSourcePropertyMappingFilter
search_fields = ["name"]
ordering = ["name"]

View File

@ -0,0 +1,114 @@
"""Source API Views"""
from django.core.cache import cache
from drf_spectacular.utils import extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.events.api.tasks import SystemTaskSerializer
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.tasks import CACHE_KEY_STATUS
class KerberosSourceSerializer(SourceSerializer):
"""Kerberos Source Serializer"""
connectivity = SerializerMethodField()
def get_connectivity(self, source: KerberosSource) -> dict[str, str] | None:
"""Get cached source connectivity"""
return cache.get(CACHE_KEY_STATUS + source.slug, None)
class Meta:
model = KerberosSource
fields = SourceSerializer.Meta.fields + [
"group_matching_mode",
"realm",
"krb5_conf",
"sync_users",
"sync_users_password",
"sync_principal",
"sync_password",
"sync_keytab",
"sync_ccache",
"connectivity",
"spnego_server_name",
"spnego_keytab",
"spnego_ccache",
"password_login_update_internal_password",
]
extra_kwargs = {
"sync_password": {"write_only": True},
"sync_keytab": {"write_only": True},
"spnego_keytab": {"write_only": True},
}
class KerberosSyncStatusSerializer(PassiveSerializer):
"""Kerberos Source sync status"""
is_running = BooleanField(read_only=True)
tasks = SystemTaskSerializer(many=True, read_only=True)
class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
"""Kerberos Source Viewset"""
queryset = KerberosSource.objects.all()
serializer_class = KerberosSourceSerializer
lookup_field = "slug"
filterset_fields = [
"name",
"slug",
"enabled",
"realm",
"sync_users",
"sync_users_password",
"sync_principal",
"spnego_server_name",
"password_login_update_internal_password",
]
search_fields = [
"name",
"slug",
"realm",
"krb5_conf",
"sync_principal",
"spnego_server_name",
]
ordering = ["name"]
@extend_schema(
responses={
200: KerberosSyncStatusSerializer(),
}
)
@action(
methods=["GET"],
detail=True,
pagination_class=None,
url_path="sync/status",
filter_backends=[],
)
def sync_status(self, request: Request, slug: str) -> Response:
"""Get source's sync status"""
source: KerberosSource = self.get_object()
tasks = list(
get_objects_for_user(request.user, "authentik_events.view_systemtask").filter(
name="kerberos_sync",
uid__startswith=source.slug,
)
)
with source.sync_lock as lock_acquired:
status = {
"tasks": tasks,
"is_running": not lock_acquired,
}
return Response(KerberosSyncStatusSerializer(status).data)

View File

@ -0,0 +1,51 @@
"""Kerberos Source Serializer"""
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.core.api.sources import (
GroupSourceConnectionSerializer,
GroupSourceConnectionViewSet,
UserSourceConnectionSerializer,
)
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.kerberos.models import (
GroupKerberosSourceConnection,
UserKerberosSourceConnection,
)
class UserKerberosSourceConnectionSerializer(UserSourceConnectionSerializer):
"""Kerberos Source Serializer"""
class Meta:
model = UserKerberosSourceConnection
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]
class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
"""Source Viewset"""
queryset = UserKerberosSourceConnection.objects.all()
serializer_class = UserKerberosSourceConnectionSerializer
filterset_fields = ["source__slug"]
search_fields = ["source__slug"]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["source__slug"]
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
"""OAuth Group-Source connection Serializer"""
class Meta(GroupSourceConnectionSerializer.Meta):
model = GroupKerberosSourceConnection
class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet):
"""Group-source connection Viewset"""
queryset = GroupKerberosSourceConnection.objects.all()
serializer_class = GroupKerberosSourceConnectionSerializer

View File

@ -0,0 +1,13 @@
"""authentik kerberos source config"""
from authentik.blueprints.apps import ManagedAppConfig
class AuthentikSourceKerberosConfig(ManagedAppConfig):
"""Authentik source kerberos app config"""
name = "authentik.sources.kerberos"
label = "authentik_sources_kerberos"
verbose_name = "authentik Sources.Kerberos"
mountpoint = "source/kerberos/"
default = True

View File

@ -0,0 +1,116 @@
"""authentik Kerberos Authentication Backend"""
import gssapi
from django.http import HttpRequest
from structlog.stdlib import get_logger
from authentik.core.auth import InbuiltBackend
from authentik.core.models import User
from authentik.lib.generators import generate_id
from authentik.sources.kerberos.models import (
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)
LOGGER = get_logger()
class KerberosBackend(InbuiltBackend):
"""Authenticate users against Kerberos realm"""
def authenticate(self, request: HttpRequest, **kwargs):
"""Try to authenticate a user via kerberos"""
if "password" not in kwargs or "username" not in kwargs:
return None
username = kwargs.pop("username")
realm = None
if "@" in username:
username, realm = username.rsplit("@", 1)
user, source = self.auth_user(username, realm, **kwargs)
if user:
self.set_method("kerberos", request, source=source)
return user
return None
def auth_user(
self, username: str, realm: str | None, password: str, **filters
) -> tuple[User | None, KerberosSource | None]:
sources = KerberosSource.objects.filter(enabled=True)
user = User.objects.filter(usersourceconnection__source__in=sources, **filters).first()
if user is not None:
# User found, let's get its connections for the sources that are available
user_source_connections = UserKerberosSourceConnection.objects.filter(
user=user, source__in=sources
)
elif realm is not None:
user_source_connections = UserKerberosSourceConnection.objects.filter(
source__in=sources, identifier=f"{username}@{realm}"
)
# no realm specified, we can't do anything
else:
user_source_connections = UserKerberosSourceConnection.objects.none()
if not user_source_connections.exists():
LOGGER.debug("no kerberos source found for user", username=username)
return None, None
for user_source_connection in user_source_connections.prefetch_related().select_related(
"source__kerberossource"
):
# User either has an unusable password,
# or has a password, but couldn't be authenticated by ModelBackend
# This means we check with a kinit to see if the Kerberos password has changed
if self.auth_user_by_kinit(user_source_connection, password):
# Password was successful in kinit to Kerberos, so we save it in database
if (
user_source_connection.source.kerberossource.password_login_update_internal_password
):
LOGGER.debug(
"Updating user's password in DB",
source=user_source_connection.source,
user=user_source_connection.user,
)
user_source_connection.user.set_password(
password, sender=user_source_connection.source
)
user_source_connection.user.save()
return user, user_source_connection.source
# Password doesn't match, onto next source
LOGGER.debug(
"failed to kinit, password invalid",
source=user_source_connection.source,
user=user_source_connection.user,
)
# No source with valid password found
LOGGER.debug("no valid kerberos source found for user", user=user)
return None, None
def auth_user_by_kinit(
self, user_source_connection: UserKerberosSourceConnection, password: str
) -> bool:
"""Attempt authentication by kinit to the source."""
LOGGER.debug(
"Attempting to kinit as user",
user=user_source_connection.user,
source=user_source_connection.source,
principal=user_source_connection.identifier,
)
with Krb5ConfContext(user_source_connection.source.kerberossource):
name = gssapi.raw.import_name(
user_source_connection.identifier.encode(), gssapi.raw.NameType.kerberos_principal
)
try:
# Use a temporary credentials cache to not interfere with whatever is defined
# elsewhere
gssapi.raw.ext_krb5.krb5_ccache_name(f"MEMORY:{generate_id(12)}".encode())
gssapi.raw.ext_password.acquire_cred_with_password(name, password.encode())
# Restore the credentials cache to what it was before
gssapi.raw.ext_krb5.krb5_ccache_name(None)
return True
except gssapi.exceptions.GSSError as exc:
LOGGER.warning("failed to kinit", exc=exc)
return False

View File

@ -0,0 +1,4 @@
[libdefaults]
dns_canonicalize_hostname = false
dns_fallback = true
rnds = false

View File

@ -0,0 +1,25 @@
"""Kerberos Connection check"""
from json import dumps
from structlog.stdlib import get_logger
from authentik.sources.kerberos.models import KerberosSource
from authentik.tenants.management import TenantCommand
LOGGER = get_logger()
class Command(TenantCommand):
"""Check connectivity to Kerberos servers for a source"""
def add_arguments(self, parser):
parser.add_argument("source_slugs", nargs="?", type=str)
def handle_per_tenant(self, **options):
sources = KerberosSource.objects.filter(enabled=True)
if options["source_slugs"]:
sources = KerberosSource.objects.filter(slug__in=options["source_slugs"])
for source in sources.order_by("slug"):
status = source.check_connection()
self.stdout.write(dumps(status, indent=4))

View File

@ -0,0 +1,25 @@
"""Kerberos Sync"""
from structlog.stdlib import get_logger
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.sync import KerberosSync
from authentik.tenants.management import TenantCommand
LOGGER = get_logger()
class Command(TenantCommand):
"""Run sync for an Kerberos Source"""
def add_arguments(self, parser):
parser.add_argument("source_slugs", nargs="+", type=str)
def handle_per_tenant(self, **options):
for source_slug in options["source_slugs"]:
source = KerberosSource.objects.filter(slug=source_slug).first()
if not source:
LOGGER.warning("Source does not exist", slug=source_slug)
continue
user_count = KerberosSync(source).sync()
LOGGER.info(f"Synced {user_count} users", slug=source_slug)

View File

@ -0,0 +1,179 @@
# Generated by Django 5.0.9 on 2024-09-23 11:27
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_core", "0039_source_group_matching_mode_alter_group_name_and_more"),
]
operations = [
migrations.CreateModel(
name="GroupKerberosSourceConnection",
fields=[
(
"groupsourceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.groupsourceconnection",
),
),
],
options={
"verbose_name": "Group Kerberos Source Connection",
"verbose_name_plural": "Group Kerberos Source Connections",
},
bases=("authentik_core.groupsourceconnection",),
),
migrations.CreateModel(
name="KerberosSource",
fields=[
(
"source_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.source",
),
),
("realm", models.TextField(help_text="Kerberos realm", unique=True)),
(
"krb5_conf",
models.TextField(
blank=True,
help_text="Custom krb5.conf to use. Uses the system one by default",
),
),
(
"sync_users",
models.BooleanField(
db_index=True,
default=False,
help_text="Sync users from Kerberos into authentik",
),
),
(
"sync_users_password",
models.BooleanField(
db_index=True,
default=True,
help_text="When a user changes their password, sync it back to Kerberos",
),
),
(
"sync_principal",
models.TextField(
blank=True, help_text="Principal to authenticate to kadmin for sync."
),
),
(
"sync_password",
models.TextField(
blank=True, help_text="Password to authenticate to kadmin for sync"
),
),
(
"sync_keytab",
models.TextField(
blank=True,
help_text="Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the form TYPE:residual",
),
),
(
"sync_ccache",
models.TextField(
blank=True,
help_text="Credentials cache to authenticate to kadmin for sync. Must be in the form TYPE:residual",
),
),
(
"spnego_server_name",
models.TextField(
blank=True,
help_text="Force the use of a specific server name for SPNEGO. Must be in the form HTTP@hostname",
),
),
(
"spnego_keytab",
models.TextField(
blank=True,
help_text="SPNEGO keytab base64-encoded or path to keytab in the form FILE:path",
),
),
(
"spnego_ccache",
models.TextField(
blank=True,
help_text="Credential cache to use for SPNEGO in form type:residual",
),
),
(
"password_login_update_internal_password",
models.BooleanField(
default=False,
help_text="If enabled, the authentik-stored password will be updated upon login with the Kerberos password backend",
),
),
],
options={
"verbose_name": "Kerberos Source",
"verbose_name_plural": "Kerberos Sources",
},
bases=("authentik_core.source",),
),
migrations.CreateModel(
name="KerberosSourcePropertyMapping",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.propertymapping",
),
),
],
options={
"verbose_name": "Kerberos Source Property Mapping",
"verbose_name_plural": "Kerberos Source Property Mappings",
},
bases=("authentik_core.propertymapping",),
),
migrations.CreateModel(
name="UserKerberosSourceConnection",
fields=[
(
"usersourceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.usersourceconnection",
),
),
("identifier", models.TextField()),
],
options={
"verbose_name": "User Kerberos Source Connection",
"verbose_name_plural": "User Kerberos Source Connections",
},
bases=("authentik_core.usersourceconnection",),
),
]

View File

@ -0,0 +1,376 @@
"""authentik Kerberos Source Models"""
import os
from pathlib import Path
from tempfile import gettempdir
from typing import Any
import gssapi
import kadmin
import pglock
from django.db import connection, models
from django.db.models.fields import b64decode
from django.http import HttpRequest
from django.shortcuts import reverse
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.core.models import (
GroupSourceConnection,
PropertyMapping,
Source,
UserSourceConnection,
UserTypes,
)
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.flows.challenge import RedirectChallenge
LOGGER = get_logger()
# python-kadmin leaks file descriptors. As such, this global is used to reuse
# existing kadmin connections instead of creating new ones, which results in less to no file
# descriptors leaks
_kadmin_connections: dict[str, Any] = {}
class KerberosSource(Source):
"""Federate Kerberos realm with authentik"""
realm = models.TextField(help_text=_("Kerberos realm"), unique=True)
krb5_conf = models.TextField(
blank=True,
help_text=_("Custom krb5.conf to use. Uses the system one by default"),
)
sync_users = models.BooleanField(
default=False, help_text=_("Sync users from Kerberos into authentik"), db_index=True
)
sync_users_password = models.BooleanField(
default=True,
help_text=_("When a user changes their password, sync it back to Kerberos"),
db_index=True,
)
sync_principal = models.TextField(
help_text=_("Principal to authenticate to kadmin for sync."), blank=True
)
sync_password = models.TextField(
help_text=_("Password to authenticate to kadmin for sync"), blank=True
)
sync_keytab = models.TextField(
help_text=_(
"Keytab to authenticate to kadmin for sync. "
"Must be base64-encoded or in the form TYPE:residual"
),
blank=True,
)
sync_ccache = models.TextField(
help_text=_(
"Credentials cache to authenticate to kadmin for sync. "
"Must be in the form TYPE:residual"
),
blank=True,
)
spnego_server_name = models.TextField(
help_text=_(
"Force the use of a specific server name for SPNEGO. Must be in the form HTTP@hostname"
),
blank=True,
)
spnego_keytab = models.TextField(
help_text=_("SPNEGO keytab base64-encoded or path to keytab in the form FILE:path"),
blank=True,
)
spnego_ccache = models.TextField(
help_text=_("Credential cache to use for SPNEGO in form type:residual"),
blank=True,
)
password_login_update_internal_password = models.BooleanField(
default=False,
help_text=_(
"If enabled, the authentik-stored password will be updated upon "
"login with the Kerberos password backend"
),
)
class Meta:
verbose_name = _("Kerberos Source")
verbose_name_plural = _("Kerberos Sources")
def __str__(self):
return f"Kerberos Source {self.name}"
@property
def component(self) -> str:
return "ak-source-kerberos-form"
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.kerberos.api.source import KerberosSourceSerializer
return KerberosSourceSerializer
@property
def property_mapping_type(self) -> type[PropertyMapping]:
return KerberosSourcePropertyMapping
@property
def icon_url(self) -> str:
icon = super().icon_url
if not icon:
return static("authentik/sources/kerberos.png")
return icon
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
return UILoginButton(
challenge=RedirectChallenge(
data={
"to": reverse(
"authentik_sources_kerberos:spnego-login",
kwargs={"source_slug": self.slug},
),
}
),
name=self.name,
icon_url=self.icon_url,
)
def ui_user_settings(self) -> UserSettingSerializer | None:
return UserSettingSerializer(
data={
"title": self.name,
"component": "ak-user-settings-source-kerberos",
"configure_url": reverse(
"authentik_sources_kerberos:spnego-login",
kwargs={"source_slug": self.slug},
),
"icon_url": self.icon_url,
}
)
@property
def sync_lock(self) -> pglock.advisory:
"""Redis lock for syncing Kerberos to prevent multiple parallel syncs happening"""
return pglock.advisory(
lock_id=f"goauthentik.io/{connection.schema_name}/sources/kerberos/sync/{self.slug}",
timeout=0,
side_effect=pglock.Return,
)
def get_base_user_properties(self, principal: str, **kwargs):
localpart, _ = principal.rsplit("@", 1)
return {
"username": localpart,
"type": UserTypes.INTERNAL,
"path": self.get_user_path(),
}
def get_base_group_properties(self, group_id: str, **kwargs):
return {
"name": group_id,
}
@property
def tempdir(self) -> Path:
"""Get temporary storage for Kerberos files"""
path = (
Path(gettempdir())
/ "authentik"
/ connection.schema_name
/ "sources"
/ "kerberos"
/ str(self.pk)
)
path.mkdir(mode=0o700, parents=True, exist_ok=True)
return path
@property
def krb5_conf_path(self) -> str | None:
"""Get krb5.conf path"""
if not self.krb5_conf:
return None
conf_path = self.tempdir / "krb5.conf"
conf_path.write_text(self.krb5_conf)
return str(conf_path)
def _kadmin_init(self) -> "kadmin.KAdmin | None":
# kadmin doesn't use a ccache for its connection
# as such, we don't need to create a separate ccache for each source
if not self.sync_principal:
return None
if self.sync_password:
return kadmin.init_with_password(
self.sync_principal,
self.sync_password,
)
if self.sync_keytab:
keytab = self.sync_keytab
if ":" not in keytab:
keytab_path = self.tempdir / "kadmin_keytab"
keytab_path.touch(mode=0o600)
keytab_path.write_bytes(b64decode(self.sync_keytab))
keytab = f"FILE:{keytab_path}"
return kadmin.init_with_keytab(
self.sync_principal,
keytab,
)
if self.sync_ccache:
return kadmin.init_with_ccache(
self.sync_principal,
self.sync_ccache,
)
return None
def connection(self) -> "kadmin.KAdmin | None":
"""Get kadmin connection"""
if str(self.pk) not in _kadmin_connections:
kadm = self._kadmin_init()
if kadm is not None:
_kadmin_connections[str(self.pk)] = self._kadmin_init()
return _kadmin_connections.get(str(self.pk), None)
def check_connection(self) -> dict[str, str]:
"""Check Kerberos Connection"""
status = {"status": "ok"}
if not self.sync_users:
return status
with Krb5ConfContext(self):
try:
kadm = self.connection()
if kadm is None:
status["status"] = "no connection"
return status
status["principal_exists"] = kadm.principal_exists(self.sync_principal)
except kadmin.KAdminError as exc:
status["status"] = str(exc)
return status
def get_gssapi_store(self) -> dict[str, str]:
"""Get GSSAPI credentials store for this source"""
ccache = self.spnego_ccache
keytab = None
if not ccache:
ccache_path = self.tempdir / "spnego_ccache"
ccache_path.touch(mode=0o600)
ccache = f"FILE:{ccache_path}"
if self.spnego_keytab:
# Keytab is of the form type:residual, use as-is
if ":" in self.spnego_keytab:
keytab = self.spnego_keytab
# Parse the keytab and write it in the file
else:
keytab_path = self.tempdir / "spnego_keytab"
keytab_path.touch(mode=0o600)
keytab_path.write_bytes(b64decode(self.spnego_keytab))
keytab = f"FILE:{keytab_path}"
store = {"ccache": ccache}
if keytab is not None:
store["keytab"] = keytab
return store
def get_gssapi_creds(self) -> gssapi.creds.Credentials | None:
"""Get GSSAPI credentials for this source"""
try:
name = None
if self.spnego_server_name:
# pylint: disable=c-extension-no-member
name = gssapi.names.Name(
base=self.spnego_server_name,
name_type=gssapi.raw.types.NameType.hostbased_service,
)
return gssapi.creds.Credentials(
usage="accept", name=name, store=self.get_gssapi_store()
)
except gssapi.exceptions.GSSError as exc:
LOGGER.warn("GSSAPI credentials failure", exc=exc)
return None
class Krb5ConfContext:
"""
Context manager to set the path to the krb5.conf config file.
"""
def __init__(self, source: KerberosSource):
self._source = source
self._path = self._source.krb5_conf_path
self._previous = None
def __enter__(self):
if not self._path:
return
self._previous = os.environ.get("KRB5_CONFIG", None)
os.environ["KRB5_CONFIG"] = self._path
def __exit__(self, *args, **kwargs):
if not self._path:
return
if self._previous:
os.environ["KRB5_CONFIG"] = self._previous
else:
del os.environ["KRB5_CONFIG"]
class KerberosSourcePropertyMapping(PropertyMapping):
"""Map Kerberos Property to User object attribute"""
@property
def component(self) -> str:
return "ak-property-mapping-source-kerberos-form"
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.kerberos.api.property_mappings import (
KerberosSourcePropertyMappingSerializer,
)
return KerberosSourcePropertyMappingSerializer
def __str__(self):
return str(self.name)
class Meta:
verbose_name = _("Kerberos Source Property Mapping")
verbose_name_plural = _("Kerberos Source Property Mappings")
class UserKerberosSourceConnection(UserSourceConnection):
"""Connection to configured Kerberos Sources."""
identifier = models.TextField()
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.kerberos.api.source_connection import (
UserKerberosSourceConnectionSerializer,
)
return UserKerberosSourceConnectionSerializer
class Meta:
verbose_name = _("User Kerberos Source Connection")
verbose_name_plural = _("User Kerberos Source Connections")
class GroupKerberosSourceConnection(GroupSourceConnection):
"""Connection to configured Kerberos Sources."""
@property
def serializer(self) -> type[Serializer]:
from authentik.sources.kerberos.api.source_connection import (
GroupKerberosSourceConnectionSerializer,
)
return GroupKerberosSourceConnectionSerializer
class Meta:
verbose_name = _("Group Kerberos Source Connection")
verbose_name_plural = _("Group Kerberos Source Connections")

View File

@ -0,0 +1,18 @@
"""LDAP Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"sources_kerberos_sync": {
"task": "authentik.sources.kerberos.tasks.kerberos_sync_all",
"schedule": crontab(minute=fqdn_rand("sources_kerberos_sync"), hour="*/2"),
"options": {"queue": "authentik_scheduled"},
},
"sources_kerberos_connectivity_check": {
"task": "authentik.sources.kerberos.tasks.kerberos_connectivity_check",
"schedule": crontab(minute=fqdn_rand("sources_kerberos_connectivity_check"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -0,0 +1,61 @@
"""authentik kerberos source signals"""
import kadmin
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.core.signals import password_changed
from authentik.events.models import Event, EventAction
from authentik.sources.kerberos.models import (
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)
from authentik.sources.kerberos.tasks import kerberos_connectivity_check, kerberos_sync_single
LOGGER = get_logger()
@receiver(post_save, sender=KerberosSource)
def sync_kerberos_source_on_save(sender, instance: KerberosSource, **_):
"""Ensure that source is synced on save (if enabled)"""
if not instance.enabled or not instance.sync_users:
return
kerberos_sync_single.delay(instance.pk)
kerberos_connectivity_check.delay(instance.pk)
@receiver(password_changed)
def kerberos_sync_password(sender, user: User, password: str, **_):
"""Connect to kerberos and update password."""
user_source_connections = UserKerberosSourceConnection.objects.select_related(
"source__kerberossource"
).filter(
user=user,
source__enabled=True,
source__kerberossource__sync_users=True,
source__kerberossource__sync_users_password=True,
)
for user_source_connection in user_source_connections:
source = user_source_connection.source.kerberossource
if source.pk == getattr(sender, "pk", None):
continue
with Krb5ConfContext(source):
try:
source.connection().getprinc(user_source_connection.identifier).change_password(
password
)
except kadmin.KAdminError as exc:
LOGGER.warning("failed to set Kerberos password", exc=exc, source=source)
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
"Failed to change password in Kerberos source due to remote error: "
f"{exc}"
),
source=source,
).set_user(user).save()
raise ValidationError("Failed to set password") from exc

View File

@ -0,0 +1,167 @@
"""Sync Kerberos users into authentik"""
from typing import Any
import kadmin
from django.core.exceptions import FieldError
from django.db import IntegrityError, transaction
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.expression.exceptions import (
PropertyMappingExpressionException,
SkipObjectException,
)
from authentik.core.models import Group, User, UserTypes
from authentik.core.sources.mapper import SourceMapper
from authentik.core.sources.matcher import Action, SourceMatcher
from authentik.events.models import Event, EventAction
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.sources.kerberos.models import (
GroupKerberosSourceConnection,
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)
class KerberosSync:
"""Sync Kerberos users into authentik"""
_source: KerberosSource
_logger: BoundLogger
_connection: "kadmin.KAdmin"
mapper: SourceMapper
user_manager: PropertyMappingManager
group_manager: PropertyMappingManager
matcher: SourceMatcher
def __init__(self, source: KerberosSource):
self._source = source
with Krb5ConfContext(self._source):
self._connection = self._source.connection()
self._messages = []
self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
self.mapper = SourceMapper(self._source)
self.user_manager = self.mapper.get_manager(User, ["principal"])
self.group_manager = self.mapper.get_manager(Group, ["group_id", "principal"])
self.matcher = SourceMatcher(
self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
)
@staticmethod
def name() -> str:
"""UI name for the type of object this class synchronizes"""
return "users"
@property
def messages(self) -> list[str]:
"""Get all UI messages"""
return self._messages
def message(self, *args, **kwargs):
"""Add message that is later added to the System Task and shown to the user"""
formatted_message = " ".join(args)
self._messages.append(formatted_message)
self._logger.warning(*args, **kwargs)
def _handle_principal(self, principal: str) -> bool:
try:
defaults = self.mapper.build_object_properties(
object_type=User,
manager=self.user_manager,
user=None,
request=None,
principal=principal,
)
self._logger.debug("Writing user with attributes", **defaults)
if "username" not in defaults:
raise IntegrityError("Username was not set by propertymappings")
action, connection = self.matcher.get_user_action(principal, defaults)
self._logger.debug("Action returned", action=action, connection=connection)
if action == Action.DENY:
return False
group_properties = {
group_id: self.mapper.build_object_properties(
object_type=Group,
manager=self.group_manager,
user=None,
request=None,
group_id=group_id,
principal=principal,
)
for group_id in defaults.pop("groups", [])
}
if action == Action.ENROLL:
user = User.objects.create(**defaults)
if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
user.set_unusable_password()
user.save()
connection.user = user
connection.save()
elif action in (Action.AUTH, Action.LINK):
user = connection.user
user.update_attributes(defaults)
else:
return False
groups: list[Group] = []
for group_id, properties in group_properties.items():
group = self._handle_group(group_id, properties)
if group:
groups.append(group)
with transaction.atomic():
user.ak_groups.remove(
*user.ak_groups.filter(groupsourceconnection__source=self._source)
)
user.ak_groups.add(*groups)
except PropertyMappingExpressionException as exc:
raise StopSync(exc, None, exc.mapping) from exc
except SkipObjectException:
return False
except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(f"Failed to create user: {str(exc)} "),
source=self._source,
principal=principal,
).save()
return False
self._logger.debug("Synced User", user=user.username)
return True
def _handle_group(
self, group_id: str, defaults: dict[str, Any | dict[str, Any]]
) -> Group | None:
action, connection = self.matcher.get_group_action(group_id, defaults)
if action == Action.DENY:
return None
if action == Action.ENROLL:
group = Group.objects.create(**defaults)
connection.group = group
connection.save()
return group
if action in (Action.AUTH, Action.LINK):
group = connection.group
group.update_attributes(defaults)
connection.save()
return group
return None
def sync(self) -> int:
"""Iterate over all Kerberos users and create authentik_core.User instances"""
if not self._source.enabled or not self._source.sync_users:
self.message("Source is disabled or user syncing is disabled for this Source")
return -1
user_count = 0
with Krb5ConfContext(self._source):
for principal in self._connection.principals():
if self._handle_principal(principal):
user_count += 1
return user_count

View File

@ -0,0 +1,68 @@
"""Kerberos Sync tasks"""
from django.core.cache import cache
from structlog.stdlib import get_logger
from authentik.events.models import SystemTask as DBSystemTask
from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask
from authentik.lib.config import CONFIG
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_string
from authentik.root.celery import CELERY_APP
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.sync import KerberosSync
LOGGER = get_logger()
CACHE_KEY_STATUS = "goauthentik.io/sources/kerberos/status/"
@CELERY_APP.task()
def kerberos_sync_all():
"""Sync all sources"""
for source in KerberosSource.objects.filter(enabled=True, sync_users=True):
kerberos_sync_single.delay(str(source.pk))
@CELERY_APP.task()
def kerberos_connectivity_check(pk: str | None = None):
"""Check connectivity for Kerberos Sources"""
# 2 hour timeout, this task should run every hour
timeout = 60 * 60 * 2
sources = KerberosSource.objects.filter(enabled=True)
if pk:
sources = sources.filter(pk=pk)
for source in sources:
status = source.check_connection()
cache.set(CACHE_KEY_STATUS + source.slug, status, timeout=timeout)
@CELERY_APP.task(
bind=True,
base=SystemTask,
# We take the configured hours timeout time by 2.5 as we run user and
# group in parallel and then membership, so 2x is to cover the serial tasks,
# and 0.5x on top of that to give some more leeway
soft_time_limit=(60 * 60 * CONFIG.get_int("sources.kerberos.task_timeout_hours")) * 2.5,
task_time_limit=(60 * 60 * CONFIG.get_int("sources.kerberos.task_timeout_hours")) * 2.5,
)
def kerberos_sync_single(self, source_pk: str):
"""Sync a single source"""
source: KerberosSource = KerberosSource.objects.filter(pk=source_pk).first()
if not source or not source.enabled:
return
try:
with source.sync_lock as lock_acquired:
if not lock_acquired:
LOGGER.debug(
"Failed to acquire lock for Kerberos sync, skipping task", source=source.slug
)
return
# Delete all sync tasks from the cache
DBSystemTask.objects.filter(name="kerberos_sync", uid__startswith=source.slug).delete()
syncer = KerberosSync(source)
syncer.sync()
self.set_status(TaskStatus.SUCCESSFUL, *syncer.messages)
except StopSync as exc:
LOGGER.warning(exception_to_string(exc))
self.set_error(exc)

View File

@ -0,0 +1,57 @@
"""Kerberos Source Auth tests"""
from django.contrib.auth.hashers import is_password_usable
from authentik.core.models import User
from authentik.lib.generators import generate_id
from authentik.sources.kerberos.auth import KerberosBackend
from authentik.sources.kerberos.models import KerberosSource, UserKerberosSourceConnection
from authentik.sources.kerberos.tests.utils import KerberosTestCase
class TestKerberosAuth(KerberosTestCase):
"""Kerberos Auth tests"""
def setUp(self):
self.source = KerberosSource.objects.create(
name="kerberos",
slug="kerberos",
realm=self.realm.realm,
sync_users=False,
sync_users_password=False,
password_login_update_internal_password=True,
)
self.user = User.objects.create(username=generate_id())
self.user.set_unusable_password()
UserKerberosSourceConnection.objects.create(
source=self.source, user=self.user, identifier=self.realm.user_princ
)
def test_auth_username(self):
"""Test auth username"""
backend = KerberosBackend()
self.assertEqual(
backend.authenticate(
None, username=self.user.username, password=self.realm.password("user")
),
self.user,
)
def test_auth_principal(self):
"""Test auth principal"""
backend = KerberosBackend()
self.assertEqual(
backend.authenticate(
None, username=self.realm.user_princ, password=self.realm.password("user")
),
self.user,
)
def test_internal_password_update(self):
"""Test internal password update"""
backend = KerberosBackend()
backend.authenticate(
None, username=self.realm.user_princ, password=self.realm.password("user")
)
self.user.refresh_from_db()
self.assertTrue(is_password_usable(self.user.password))

View File

@ -0,0 +1,78 @@
"""Kerberos Source SPNEGO tests"""
from base64 import b64decode, b64encode
from pathlib import Path
import gssapi
from django.urls import reverse
from authentik.core.tests.utils import create_test_admin_user
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.tests.utils import KerberosTestCase
class TestSPNEGOSource(KerberosTestCase):
"""Kerberos Source SPNEGO tests"""
def setUp(self):
self.source = KerberosSource.objects.create(
name="test",
slug="test",
spnego_keytab=b64encode(Path(self.realm.http_keytab).read_bytes()).decode(),
)
# Force store creation early
self.source.get_gssapi_store()
def test_api_read(self):
"""Test reading a source"""
self.client.force_login(create_test_admin_user())
response = self.client.get(
reverse(
"authentik_api:kerberossource-detail",
kwargs={
"slug": self.source.slug,
},
)
)
self.assertEqual(response.status_code, 200)
def test_source_login(self):
"""test login view"""
response = self.client.get(
reverse(
"authentik_sources_kerberos:spnego-login",
kwargs={"source_slug": self.source.slug},
)
)
self.assertEqual(response.status_code, 302)
endpoint = response.headers["Location"]
response = self.client.get(endpoint)
self.assertEqual(response.status_code, 401)
self.assertEqual(response.headers["WWW-Authenticate"], "Negotiate")
server_name = gssapi.names.Name("HTTP/testserver@")
client_creds = gssapi.creds.Credentials(
usage="initiate", store={"ccache": self.realm.ccache}
)
client_ctx = gssapi.sec_contexts.SecurityContext(
name=server_name, usage="initiate", creds=client_creds
)
status = 401
server_token = None
while status == 401 and not client_ctx.complete: # noqa: PLR2004
client_token = client_ctx.step(server_token)
if not client_token:
break
response = self.client.get(
endpoint,
headers={"Authorization": f"Negotiate {b64encode(client_token).decode('ascii')}"},
)
status = response.status_code
if status == 401: # noqa: PLR2004
server_token = b64decode(response.headers["WWW-Authenticate"][9:].strip())
# 400 because no enroll flow
self.assertEqual(status, 400)

View File

@ -0,0 +1,75 @@
"""Kerberos Source sync tests"""
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import User
from authentik.lib.generators import generate_id
from authentik.sources.kerberos.models import KerberosSource, KerberosSourcePropertyMapping
from authentik.sources.kerberos.sync import KerberosSync
from authentik.sources.kerberos.tasks import kerberos_sync_all
from authentik.sources.kerberos.tests.utils import KerberosTestCase
class TestKerberosSync(KerberosTestCase):
"""Kerberos Sync tests"""
@apply_blueprint("system/sources-kerberos.yaml")
def setUp(self):
self.source: KerberosSource = KerberosSource.objects.create(
name="kerberos",
slug="kerberos",
realm=self.realm.realm,
sync_users=True,
sync_users_password=True,
sync_principal=self.realm.admin_princ,
sync_password=self.realm.password("admin"),
)
self.source.user_property_mappings.set(
KerberosSourcePropertyMapping.objects.filter(
managed__startswith="goauthentik.io/sources/kerberos/user/default/"
)
)
def test_default_mappings(self):
"""Test default mappings"""
KerberosSync(self.source).sync()
self.assertTrue(
User.objects.filter(username=self.realm.user_princ.rsplit("@", 1)[0]).exists()
)
self.assertFalse(
User.objects.filter(username=self.realm.nfs_princ.rsplit("@", 1)[0]).exists()
)
def test_sync_mapping(self):
"""Test property mappings"""
noop = KerberosSourcePropertyMapping.objects.create(
name=generate_id(), expression="return {}"
)
email = KerberosSourcePropertyMapping.objects.create(
name=generate_id(), expression='return {"email": principal.lower()}'
)
dont_sync_service = KerberosSourcePropertyMapping.objects.create(
name=generate_id(),
expression='if "/" in principal:\n return {"username": None}\nreturn {}',
)
self.source.user_property_mappings.set([noop, email, dont_sync_service])
KerberosSync(self.source).sync()
self.assertTrue(
User.objects.filter(username=self.realm.user_princ.rsplit("@", 1)[0]).exists()
)
self.assertEqual(
User.objects.get(username=self.realm.user_princ.rsplit("@", 1)[0]).email,
self.realm.user_princ.lower(),
)
self.assertFalse(
User.objects.filter(username=self.realm.nfs_princ.rsplit("@", 1)[0]).exists()
)
def test_tasks(self):
"""Test Scheduled tasks"""
kerberos_sync_all.delay().get()
self.assertTrue(
User.objects.filter(username=self.realm.user_princ.rsplit("@", 1)[0]).exists()
)

View File

@ -0,0 +1,40 @@
"""Kerberos Source test utils"""
import os
from copy import deepcopy
from time import sleep
from k5test import realm
from rest_framework.test import APITestCase
class KerberosTestCase(APITestCase):
"""Kerberos Test Case"""
@classmethod
def setUpClass(cls):
cls.realm = realm.K5Realm(start_kadmind=True)
cls.realm.http_princ = f"HTTP/testserver@{cls.realm.realm}"
cls.realm.http_keytab = os.path.join(cls.realm.tmpdir, "http_keytab")
cls.realm.addprinc(cls.realm.http_princ)
cls.realm.extract_keytab(cls.realm.http_princ, cls.realm.http_keytab)
cls._saved_env = deepcopy(os.environ)
for k, v in cls.realm.env.items():
os.environ[k] = v
# Wait for everything to start correctly
# Otherwise leads to flaky tests
sleep(5)
@classmethod
def tearDownClass(cls):
cls.realm.stop()
del cls.realm
for k in deepcopy(os.environ):
if k in cls._saved_env:
os.environ[k] = cls._saved_env[k]
else:
del os.environ[k]
cls._saved_env = None

View File

@ -0,0 +1,22 @@
"""Kerberos Source urls"""
from django.urls import path
from authentik.sources.kerberos.api.property_mappings import KerberosSourcePropertyMappingViewSet
from authentik.sources.kerberos.api.source import KerberosSourceViewSet
from authentik.sources.kerberos.api.source_connection import (
GroupKerberosSourceConnectionViewSet,
UserKerberosSourceConnectionViewSet,
)
from authentik.sources.kerberos.views import SPNEGOView
urlpatterns = [
path("<slug:source_slug>/", SPNEGOView.as_view(), name="spnego-login"),
]
api_urlpatterns = [
("propertymappings/source/kerberos", KerberosSourcePropertyMappingViewSet),
("sources/user_connections/kerberos", UserKerberosSourceConnectionViewSet),
("sources/group_connections/kerberos", GroupKerberosSourceConnectionViewSet),
("sources/kerberos", KerberosSourceViewSet),
]

View File

@ -0,0 +1,181 @@
"""Kerberos source SPNEGO views"""
from base64 import b64decode, b64encode
import gssapi
from django.core.cache import cache
from django.core.exceptions import SuspiciousOperation
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.sources.flow_manager import SourceFlowManager
from authentik.sources.kerberos.models import (
GroupKerberosSourceConnection,
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)
LOGGER = get_logger()
SPNEGO_REQUEST_STATUS = 401
WWW_AUTHENTICATE = "WWW-Authenticate"
HTTP_AUTHORIZATION = "Authorization"
NEGOTIATE = "Negotiate"
SPNEGO_STATE_CACHE_PREFIX = "goauthentik.io/sources/spnego"
SPNEGO_STATE_CACHE_TIMEOUT = 60 * 5 # 5 minutes
def add_negotiate_to_response(
response: HttpResponse, token: str | bytes | None = None
) -> HttpResponse:
if isinstance(token, str):
token = token.encode()
response[WWW_AUTHENTICATE] = (
NEGOTIATE if token is None else f"{NEGOTIATE} {b64encode(token).decode('ascii')}"
)
return response
class SPNEGOView(View):
"""SPNEGO login"""
source: KerberosSource
def challenge(self, request, token: str | bytes | None = None) -> HttpResponse:
"""Get SNPEGO challenge response"""
response = render(
request,
"if/error.html",
context={
"title": _("SPNEGO authentication required"),
"message": _(
"""
Make sure you have valid tickets (obtainable via kinit)
and configured the browser correctly.
Please contact your administrator.
"""
),
},
status=401,
)
return add_negotiate_to_response(response, token)
def get_authstr(self, request) -> str | None:
"""Get SPNEGO authentication string from headers"""
authorization_header = request.headers.get(HTTP_AUTHORIZATION, "")
if NEGOTIATE.lower() not in authorization_header.lower():
return None
auth_tuple = authorization_header.split(" ", 1)
if not auth_tuple or auth_tuple[0].lower() != NEGOTIATE.lower():
return None
if len(auth_tuple) != 2: # noqa: PLR2004
raise SuspiciousOperation("Malformed authorization header")
return auth_tuple[1]
def new_state(self) -> str:
"""Generate request state"""
return get_random_string(32)
def get_server_ctx(self, key: str) -> gssapi.sec_contexts.SecurityContext | None:
"""Get GSSAPI server context from cache or create it"""
server_creds = self.source.get_gssapi_creds()
if server_creds is None:
return None
state = cache.get(f"{SPNEGO_STATE_CACHE_PREFIX}/{key}", None)
if state:
# pylint: disable=c-extension-no-member
return gssapi.sec_contexts.SecurityContext(
base=gssapi.raw.sec_contexts.import_sec_context(state),
)
return gssapi.sec_contexts.SecurityContext(creds=server_creds, usage="accept")
def set_server_ctx(self, key: str, ctx: gssapi.sec_contexts.SecurityContext):
"""Store the GSSAPI server context in cache"""
cache.set(f"{SPNEGO_STATE_CACHE_PREFIX}/{key}", ctx.export(), SPNEGO_STATE_CACHE_TIMEOUT)
# pylint: disable=too-many-return-statements
def dispatch(self, request, *args, **kwargs) -> HttpResponse:
"""Process SPNEGO request"""
self.source: KerberosSource = get_object_or_404(
KerberosSource,
slug=kwargs.get("source_slug", ""),
enabled=True,
)
qstring = request.GET if request.method == "GET" else request.POST
state = qstring.get("state", None)
if not state:
return redirect(
reverse(
"authentik_sources_kerberos:spnego-login",
kwargs={"source_slug": self.source.slug},
)
+ f"?state={self.new_state()}"
)
authstr = self.get_authstr(request)
if not authstr:
LOGGER.debug("authstr not present, sending challenge")
return self.challenge(request)
try:
in_token = b64decode(authstr)
except (TypeError, ValueError):
return self.challenge(request)
with Krb5ConfContext(self.source):
server_ctx = self.get_server_ctx(state)
if not server_ctx:
return self.challenge(request)
try:
out_token = server_ctx.step(in_token)
except gssapi.exceptions.GSSError as exc:
LOGGER.debug("GSSAPI security context failure", exc=exc)
return self.challenge(request)
if not server_ctx.complete or server_ctx.initiator_name is None:
self.set_server_ctx(state, server_ctx)
return self.challenge(request, out_token)
def name_to_str(n: gssapi.names.Name) -> str:
return n.display_as(n.name_type)
identifier = name_to_str(server_ctx.initiator_name)
context = {
"spnego_info": {
"initiator_name": name_to_str(server_ctx.initiator_name),
"target_name": name_to_str(server_ctx.target_name),
"mech": str(server_ctx.mech),
"actual_flags": server_ctx.actual_flags,
},
}
response = SPNEGOSourceFlowManager(
source=self.source,
request=request,
identifier=identifier,
user_info={
"principal": identifier,
**context,
},
policy_context=context,
).get_flow()
return add_negotiate_to_response(response, out_token)
class SPNEGOSourceFlowManager(SourceFlowManager):
"""Flow manager for Kerberos SPNEGO sources"""
user_connection_type = UserKerberosSourceConnection
group_connection_type = GroupKerberosSourceConnection

View File

@ -43,7 +43,7 @@ class LDAPBackend(InbuiltBackend):
if source.password_login_update_internal_password:
# Password given successfully binds to LDAP, so we save it in our Database
LOGGER.debug("Updating user's password in DB", user=user)
user.set_password(password, signal=False)
user.set_password(password, sender=source)
user.save()
return user
# Password doesn't match

View File

@ -62,6 +62,8 @@ def ldap_sync_password(sender, user: User, password: str, **_):
if not sources.exists():
return
source = sources.first()
if source.pk == getattr(sender, "pk", None):
return
if not LDAPPasswordChanger.should_check_user(user):
return
try:

View File

@ -1,6 +1,6 @@
# Generated by Django 5.0.9 on 2024-10-10 15:45
from django.db import migrations
from django.db import migrations, models
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
@ -23,4 +23,22 @@ class Migration(migrations.Migration):
operations = [
migrations.RunPython(fix_X509SubjectName),
migrations.AlterField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View File

@ -9,7 +9,6 @@ from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import CharField, ChoiceField, IntegerField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet
@ -197,7 +196,6 @@ class DuoDeviceViewSet(
class DuoAdminDeviceViewSet(ModelViewSet):
"""Viewset for Duo authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = DuoDevice.objects.all()
serializer_class = DuoDeviceSerializer
search_fields = ["name"]

View File

@ -3,7 +3,6 @@
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
@ -76,7 +75,6 @@ class SMSDeviceViewSet(
class SMSAdminDeviceViewSet(ModelViewSet):
"""Viewset for sms authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = SMSDevice.objects.all()
serializer_class = SMSDeviceSerializer
search_fields = ["name"]

View File

@ -3,7 +3,6 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
@ -80,7 +79,6 @@ class StaticDeviceViewSet(
class StaticAdminDeviceViewSet(ModelViewSet):
"""Viewset for static authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = StaticDevice.objects.all()
serializer_class = StaticDeviceSerializer
search_fields = ["name"]

View File

@ -4,7 +4,6 @@ from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.fields import ChoiceField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
@ -72,7 +71,6 @@ class TOTPDeviceViewSet(
class TOTPAdminDeviceViewSet(ModelViewSet):
"""Viewset for totp authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = TOTPDevice.objects.all()
serializer_class = TOTPDeviceSerializer
search_fields = ["name"]

View File

@ -8,7 +8,7 @@ from django.http.response import Http404
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as __
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import CharField
from rest_framework.fields import CharField, DateTimeField
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
from webauthn import options_to_json
@ -45,6 +45,7 @@ class DeviceChallenge(PassiveSerializer):
device_class = CharField()
device_uid = CharField()
challenge = JSONDictField()
last_used = DateTimeField(allow_null=True)
def get_challenge_for_device(

View File

@ -217,6 +217,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
"device_class": device_class,
"device_uid": device.pk,
"challenge": get_challenge_for_device(self.request, stage, device),
"last_used": device.last_used,
}
)
challenge.is_valid()
@ -237,6 +238,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
self.request,
self.executor.current_stage,
),
"last_used": None,
}
)
challenge.is_valid()

View File

@ -107,6 +107,7 @@ class AuthenticatorValidateStageSMSTests(FlowTestCase):
"device_class": "sms",
"device_uid": str(device.pk),
"challenge": {},
"last_used": None,
},
},
)

View File

@ -169,6 +169,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
"device_class": "baz",
"device_uid": "quox",
"challenge": {},
"last_used": None,
}
},
)
@ -188,6 +189,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
"device_class": "static",
"device_uid": "1",
"challenge": {},
"last_used": None,
},
},
)

View File

@ -274,6 +274,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
"last_used": None,
}
]
session[SESSION_KEY_PLAN] = plan
@ -352,6 +353,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
"last_used": None,
}
]
session[SESSION_KEY_PLAN] = plan
@ -432,6 +434,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
"last_used": None,
}
]
session[SESSION_KEY_PLAN] = plan

View File

@ -3,7 +3,6 @@
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
@ -48,7 +47,6 @@ class WebAuthnDeviceViewSet(
class WebAuthnAdminDeviceViewSet(ModelViewSet):
"""Viewset for WebAuthn authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = WebAuthnDevice.objects.all()
serializer_class = WebAuthnDeviceSerializer
search_fields = ["name"]

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