Compare commits

...

113 Commits

Author SHA1 Message Date
b3b50c5914 core: enforce unique group name on database level
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-11 18:12:39 +01:00
006766cd3c website: bump docusaurus-plugin-openapi-docs from 4.2.0 to 4.3.0 in /website (#12322)
website: bump docusaurus-plugin-openapi-docs in /website

Bumps [docusaurus-plugin-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/tree/HEAD/packages/docusaurus-plugin-openapi-docs) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/releases)
- [Changelog](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/commits/v4.3.0/packages/docusaurus-plugin-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-plugin-openapi-docs
  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-12-11 15:01:50 +01:00
a79cda924b website: bump docusaurus-theme-openapi-docs from 4.2.0 to 4.3.0 in /website (#12323)
website: bump docusaurus-theme-openapi-docs in /website

Bumps [docusaurus-theme-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/tree/HEAD/packages/docusaurus-theme-openapi-docs) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/releases)
- [Changelog](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/commits/v4.3.0/packages/docusaurus-theme-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-theme-openapi-docs
  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-12-11 14:57:10 +01:00
9d0901effd core: bump goauthentik.io/api/v3 from 3.2024104.2 to 3.2024105.1 (#12324)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024104.2 to 3.2024105.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.2024104.2...v3.2024105.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-12-11 14:57:01 +01:00
89b6a2a2f2 core: bump debugpy from 1.8.9 to 1.8.10 (#12325)
Bumps [debugpy](https://github.com/microsoft/debugpy) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/microsoft/debugpy/releases)
- [Commits](https://github.com/microsoft/debugpy/compare/v1.8.9...v1.8.10)

---
updated-dependencies:
- dependency-name: debugpy
  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-12-11 14:56:46 +01:00
12d9966604 website/integrations: add espoCRM (#11622)
* Create index.md (espoCRM)

Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* Update sidebarsIntegrations.js

added espoCRM integration

* Update index.md

* Update index.md

* Update website/sidebarsIntegrations.js

Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* Update website/integrations/services/espoCRM/index.md

fix integration capitalization

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* Update website/integrations/services/espoCRM/index.md

fix integration capitalization

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* Update index.md

Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>

* applying new suggestions

* fix formatting

* fix formatting again

---------

Signed-off-by: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 22:21:48 +01:00
accc88fcdd web: bump API Client version (#12320)
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-12-10 19:44:21 +01:00
e5dd923333 release: 2024.10.5 (#12319)
* release: 2024.10.5

* manually bump aws version

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 19:20:01 +01:00
29f53fd3a4 website/docs: update and rearchitect the oauth2 provider docs (#11916)
* new topics

* update diagram

* more writing and sidebar entries

* fix file name

* more link fixes

* fix header level

* Optimised images with calibre/image-actions

* rewrite stuff

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

* reorganize more

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

* messed w/ diagram and added 3 categories

* fixed anchor

* removed whole line

* add link

* more rearrangements

* more content

* edits

* more polishes, rest of Jens' knowledge dump

* more content

* tweaks

* tweak

* argh

* synch with main

* tweaks to test merge

* cleanup

* offline_access

* polish

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tana@goauthentik.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 12:03:07 -06:00
cbe5a0d2c8 website/docs: update 2024.10.5 release notes (#12318)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 18:19:58 +01:00
81b3662046 flows: better test stage's challenge responses (#12316)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 17:46:03 +01:00
12ac058547 enterprise/stages/authenticator_endpoint_gdtc: don't set frame options globally (#12311)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 15:17:00 +01:00
69cd0ef565 stages/identification: fix invalid challenge warning when no captcha stage is set (#12312)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 15:16:44 +01:00
83edb0dcb8 website/docs: prepare 2024.10.5 release notes (#12309)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 14:15:24 +01:00
0c80b1b8c3 website: bump nanoid from 3.3.7 to 3.3.8 in /website (#12307)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 13:43:40 +01:00
47e330d08a flows: silent authz flow (#12213)
* flows: add FlowPlan .to_redirect helper to redirect to flow executor

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

* flows: add initial silent flow executor

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

* more cleanup

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

* refactor and add tests

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

* how'd that happen

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

* fix most tests

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

* don't set allowed_silent_types if we cant transmit data via URL

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

* fix stage not being set early enough

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

* fix OAuthDeviceCodeFinishStage being marked able-to-be-skipped-to when it is not

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

* fix more tests

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

* dont skip on rac for now

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

* add support for SAML redirect

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-10 13:43:22 +01:00
97676d28a7 root: use healthcheck in depends_on for postgres and redis (#12301)
Docker Compose Updates Healthcheck

The Healthcheck is not used for the depends_on option. This change ensures all dependencies are all ready to work before starting worker and server container.

Signed-off-by: Kaindl Network <82705244+kaindlnetwork@users.noreply.github.com>
2024-12-10 13:10:57 +01:00
b9435870c0 ci: ensure mark jobs always run and reflect correct status (#12288)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-10 13:09:41 +01:00
81ae02e623 enterprise: allow deletion/modification of users when in read-only mode (#12289)
* enterprise: allow deletion/modification of users when in read-only mode

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

* actually 10.5+

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>

---------

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-12-10 13:07:50 +01:00
28a23110c2 web/flows: resize captcha iframes (#12260)
* 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: streamline CaptchaStage

# What

This commit:

1. Replaces the mass of `if () { if() { if() } }` with two state tables:
  - One for `render()`
  - One for `renderBody()`

2. Breaks each Captcha out into "interactive" and "executive" versions
3. Creates a handler table for each Captcha type
4. Replaces the `.forEach` expression with a `for` loop.
5. Move `updated` to the end of the class.
6. Make captchDocument and captchaFrame constructed-on-demand with a cache.
7. Remove a lot of `@state` handlers
8. Give IframeMessageEvent its own type.
9. Removed `this.scriptElement`
10. Replaced `window.removeEventListener` with an `AbortController()`
# Why

1. **Replacing `if` trees with a state table.** The logic of the original was really hard to follow.
   With the state table, we can clearly see now that for the `render()` function, we care about the
   Boolean flags `[embedded, challenged, interactive]` and have appropriate effects for each. With
   `renderBody()`, we can see that we care about the Boolean flags `[hasError, challenged]`, and can
   see the appropriate effects for each one.

2. (and 3.) **Breaking each Captcha clause into separate handlers.** Again, the logic was convoluted,
   when what we really care about is "Does this captcha have a corresponding handler attached to
   `window`, and, if so, should we run the interactive or executive version of it?" By putting all
   of that into a table of `[name, challenge, execute]`, we can clearly see what's being handled
   when.

4. **Replacing `foreach()` with `for()`**: [You cannot use a `forEach()` with async
   expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#:~:text=does%20not%20wait%20for%20promises).
   If you need asynchronous behavior in an ordered loop, `for()` is the safest way to handle it; if
   you need asynchronous behavior from multiple promises, `Promise.allSettled(handlers.map())` is
   the way to go.

   I tried to tell if this function *meant* to run every handler it found simultaneously, or if it
   meant to test them in order; I went with the second option, breaking and exiting the loop once a
   handler had run successfully.

5. **Reordered the code a bit**. We're trying to evolve a pattern in our source code: styles,
   properties, states, internal variables, constructor, getters & setters that are not `@property()`
   or `@state()`, DOM-related lifecycle handlers, event handlers, pre-render lifecycle handlers,
   renderers, and post-render lifecycle handlers. Helper methods (including subrenderers) go above
   the method(s) they help.

6. **Constructing Elements on demand with a cache**. It is not guaranteed that we will actually need
   either of those. Constructing them on demand with a cache is both performant and cleaner.
   Likewise, I removed these from the Lit object's `state()` table, since they're constructed once
   and never change over the lifetime of an instance of `ak-stage-captcha`.

9. **Remove `this.scriptElement`**: It was never referenced outside the one function where it was used.

10. **Remove `removeEventListener()`**: `AbortController()` is a bit more verbose for small event
    handler collections, but it's considered much more reliable and much cleaner.

* Didn't save the extracted ListenerController.
2024-12-09 09:11:04 -08:00
a117918cd6 website/docs: add page about the Cobalt pentest (#12249)
* draft for collab

* links

* added link to see all audits

* corrections and fix explanations

Signed-off-by: Fletcher Heisler <fheisler@users.noreply.github.com>

---------

Signed-off-by: Fletcher Heisler <fheisler@users.noreply.github.com>
Co-authored-by: Tana M Berry <tana@goauthentik.com>
Co-authored-by: Fletcher Heisler <fheisler@users.noreply.github.com>
2024-12-09 07:57:34 -06:00
f1a548f941 core: bump aws-cdk-lib from 2.171.1 to 2.172.0 (#12296)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.171.1 to 2.172.0.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/v2.172.0/CHANGELOG.v2.md)
- [Commits](https://github.com/aws/aws-cdk/compare/v2.171.1...v2.172.0)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  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-12-09 14:39:51 +01:00
021c0f7cb9 website: bump aws-cdk from 2.171.1 to 2.172.0 in /website (#12295)
Bumps [aws-cdk](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk) from 2.171.1 to 2.172.0.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/v2.172.0/CHANGELOG.v2.md)
- [Commits](https://github.com/aws/aws-cdk/commits/v2.172.0/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  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-12-09 14:39:40 +01:00
c09ce06812 core: bump sentry-sdk from 2.19.1 to 2.19.2 (#12297)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.19.1 to 2.19.2.
- [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.19.1...2.19.2)

---
updated-dependencies:
- dependency-name: sentry-sdk
  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-12-09 14:39:14 +01:00
e0aa588e60 core: bump coverage from 7.6.8 to 7.6.9 (#12299)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.8 to 7.6.9.
- [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.8...7.6.9)

---
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-12-09 14:35:30 +01:00
e842a73f3b core, web: update translations (#12290)
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-12-07 21:13:41 +02:00
98c43447ff root: fix override locale only if it is not empty (#12283)
* update impersonate middleware

override language only if it's not empty

Signed-off-by: Zhang Jie <13131481+j-z10@users.noreply.github.com>

* Update brands locale scope

Signed-off-by: Zhang Jie <13131481+j-z10@users.noreply.github.com>

---------

Signed-off-by: Zhang Jie <13131481+j-z10@users.noreply.github.com>
2024-12-06 23:58:02 +02:00
eef1237297 translate: Updates for file web/xliff/en.xlf in fr (#12276)
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-12-06 22:13:08 +02:00
dea0a34dd9 core: bump twilio from 9.3.7 to 9.3.8 (#12282)
Bumps [twilio](https://github.com/twilio/twilio-python) from 9.3.7 to 9.3.8.
- [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.7...9.3.8)

---
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-12-06 22:12:04 +02:00
f3359fb4d0 website: bump path-to-regexp and express in /website (#12279)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `path-to-regexp` from 0.1.10 to 1.9.0
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v1.9.0)

Updates `express` from 4.21.1 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-06 22:11:51 +02:00
d344db9c2a core: bump sentry-sdk from 2.19.0 to 2.19.1 (#12280)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.19.0 to 2.19.1.
- [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.19.0...2.19.1)

---
updated-dependencies:
- dependency-name: sentry-sdk
  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-12-06 22:11:41 +02:00
13b1811268 core: bump ruff from 0.8.1 to 0.8.2 (#12281)
* core: bump ruff from 0.8.1 to 0.8.2

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.1 to 0.8.2.
- [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.8.1...0.8.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

* format

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

* format v2

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-12-06 22:11:31 +02:00
84bc0b6fdd website/docs: fix lint (#12287) 2024-12-06 20:09:16 +00:00
5c2853bf73 website/integrations: netbird: fix redirect URI regex (#12284) 2024-12-06 15:03:52 +00:00
242546e621 web: simplify ?inline handler for Storybook (#12246)
* 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: simplify `?inline` handler for Storybook

# What

- Revise the `?inline` handler for Storybook
- Enable headless test runs of E2E
- Reduce headless testing to single instances

# Why

## `?inline` handling

Vite-for-Storybook-for-Web-Components has a requirement that all component CSS imports be
suffixed with an `?inline` argument so Vite knows to put the CSS into the component and
not inject it into the document head.

This `?inline` argument is an implementation detail of Storybook. It would be irrelevant clutter
added to our codebase. We were using `rollup-plugin-modify` to find every instance of an
import-to-component, but the implementation was clunky and involved scanning the source code
manually.

`rollup-plugin-modify` version 3 has regular expressions and takes a function as an argument. This
allows us to generate the CSS import maps on-the-fly when Storybook is run, eliminating a fragile
build step.  We can also remove the source code scanner for those imports.

## Changes to testing

It's just nice to be able to run the E2E tests headlessly, without them eating up your screen real
estate, flashing, or grabbing your mouse.

WebdriverIO's testing of Web Components is new and, as we've seen, a bit cranky. The WebdriverIO
team currently recommends not running the tests in parallel. We only have about 70 tests so far, and
they're fairly speedy, especially when you don't have to invoke a browser session for every test.
2024-12-05 10:16:10 -08:00
b75672ff0e website/docs: Update Traefik middleware example to reflect latest version of Traefik (#12267)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-05 18:09:13 +00:00
672ba72c8a website/docs: add . in https://netbird.company* (#12166)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-05 18:06:06 +00:00
d618f48bff core: bump goauthentik.io/api/v3 from 3.2024104.1 to 3.2024104.2 (#12263)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024104.1 to 3.2024104.2.
- [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.2024104.1...v3.2024104.2)

---
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-12-05 15:02:03 +02:00
716f2dff74 core: bump pydantic from 2.10.2 to 2.10.3 (#12262)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.2 to 2.10.3.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.2...v2.10.3)

---
updated-dependencies:
- dependency-name: pydantic
  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-12-05 15:01:11 +02:00
fc8a0b5ce0 core: bump github.com/getsentry/sentry-go from 0.29.1 to 0.30.0 (#12264)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.29.1 to 0.30.0.
- [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.1...v0.30.0)

---
updated-dependencies:
- dependency-name: github.com/getsentry/sentry-go
  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-12-05 14:58:53 +02:00
2b079816bb core, web: update translations (#12268)
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-12-05 14:58:35 +02:00
eded745332 website: bump @types/react from 18.3.12 to 18.3.13 in /website (#12269)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.3.12 to 18.3.13.
- [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-12-05 14:58:04 +02:00
1740ab938d website: bump prettier from 3.4.1 to 3.4.2 in /website (#12270)
Bumps [prettier](https://github.com/prettier/prettier) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: prettier
  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-12-05 14:57:52 +02:00
480f305f29 ci: bump actions/attest-build-provenance from 1 to 2 (#12271)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 1 to 2.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v1...v2)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 14:57:28 +02:00
90cc36fdee core: bump golang.org/x/sync from 0.9.0 to 0.10.0 (#12272)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/sync/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  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-12-05 14:54:52 +02:00
cf5b951a8c core: bump django from 5.0.9 to 5.0.10 (#12273)
Bumps [django](https://github.com/django/django) from 5.0.9 to 5.0.10.
- [Commits](https://github.com/django/django/compare/5.0.9...5.0.10)

---
updated-dependencies:
- dependency-name: django
  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-12-05 14:54:39 +02:00
9a9d8e4cf7 core: bump webauthn from 2.3.0 to 2.4.0 (#12274)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: webauthn
  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-12-05 14:53:20 +02:00
695de453ec website/integrations: add The Lounge (#11971)
* Add The Lounge Integration guide

Signed-off-by: Michael Brünen <34708235+OddMagnet@users.noreply.github.com>

* Add The Lounge to sidebar, fix Note section formatting

* Fix folder name (1/2)

* Fix folder name (2/2)

* Update website/integrations/services/thelounge/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Michael Brünen <34708235+OddMagnet@users.noreply.github.com>

* Update website/integrations/services/thelounge/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Michael Brünen <34708235+OddMagnet@users.noreply.github.com>

* Fix cutoff sentence

Signed-off-by: Michael Brünen <34708235+OddMagnet@users.noreply.github.com>

* Linter fixes

---------

Signed-off-by: Michael Brünen <34708235+OddMagnet@users.noreply.github.com>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-12-04 13:21:26 -06:00
d3a581b614 core: bump python-kadmin-rs from 0.3.0 to 0.4.0 (#12257) 2024-12-04 10:38:36 +02:00
1623885dc6 root: fix health status code (#12255) 2024-12-03 17:59:16 +02:00
0670bc8253 ci: fix should_push always being false (#12252)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-03 12:56:36 +02:00
2074944b6a web: bump API Client version (#12251)
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-12-03 11:59:47 +02:00
19488b7b9e providers/oauth2: Add provider federation between OAuth2 Providers (#12083)
* rename + add field

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

* initial implementation

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>

* refactor

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

* rework source cc tests

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

* add tests

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

* update docs

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

* re-migrate

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

* fix a

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>

---------

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-12-03 11:57:10 +02:00
4aeb7c8a84 website/integrations: mastodon: set correct uid field (#11945)
* website/integrations: mastodon: set correct uid field

Setting the `OIDC_UID_FIELD` as `sub` created users on Mastodon with random usernames. Once this was changed to `preferred_username`, new users were created with their usernames set in authentik. My assumption is that users would wish to have the same username rather than have a random one generated.

Signed-off-by: 4d62 <github-user@sdko.org>

* website/integrations: mastodon: apply review suggestions

A: https://github.com/goauthentik/authentik/pull/11945#pullrequestreview-2427160470
B: https://github.com/goauthentik/authentik/pull/11945#discussion_r1837134142

Signed-off-by: 4d62 <github-user@sdko.org>

---------

Signed-off-by: 4d62 <github-user@sdko.org>
2024-12-02 21:49:18 -06:00
e077a5c18f web/admin: bugfix: dual select initialization revision (#12051)
* 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.

* Start of dual select revision process.

* Progress.

* Made the RuleFormHelper's dualselect conform.

* Providers and Selectors harmonized for sources.

* web/bugfix/dual-select-full-options

# What

- Replaces the dual-select "selected" list mechanism with a more comprehensive (if computationally
  expensive) version that is correct.

# How

In the previous iteration, each dual select controller gets a *provider* and a *selector*; the
latter keeps the keys of all the objects a specific instance may have, and marks those objects as
"selected" when they appear in the dual-selects "selected" panel.

In order to distinguish between "selected on the existing instance" and "selected by the user," the
*selector* only runs at construction time, creating a unified "selected" list; this is standard and
allows for a uniform experience of adding and deleting items. Unfortunately, this means that the
"selected" items, because their displays are crafted bespoke, are only chosen from those available
at construction. If there are selected items later in the paginated collection, they will not be
marked as selected.

This defeats the purpose of having a paginated multi-select!

The correct way to do this is to retrieve every item pased to the *selector* and use the same
algorithm to craft the views in both windows.

For every instance of Dual Select with dynamic selection, the *provider* and *selector* have been
put in a separate file (usually suffixed as a `*FormHelper.ts` file); the algorithm by which an item is
crafted for use by DualSelect has been broken out into a small function (usually named
`*toSelect()`). The *provider* works as before. The *selector* takes every instance key passed to it
and runs a `Promise.allSettled(...*Retrieve({ uuid: instanceId }))` on them, mapping them onto the
`selected` collection using the same `*toSelect()`, so they resemble the possibilities in every way.

# Lessons

This exercise emphasizes just how much sheer *repetition* the Django REST API creates on the client
side.  Every Helper file is a copy-pasta of a sibling, with only a few minor changes:

- How the objects are turned into displays for DualSelect
- The type and calls being used;
- The field on which retrival is defined
- The defaulting rule.

There are 19 `*FormHelper` files, and each one is 50 lines long.  That's 950 lines of code.
Of those 950 lines of code, 874 of those lines are *complete duplicates* of those in the other
FormHelper files.  Only 76 lines are unique.

This language really needs macros.  That, or I need to seriously level up my Typescript and figure
out how to make this whole thing a lot smarter.

* order fields by field_key and order

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-12-02 08:30:08 -08:00
248fcdd1bf web: update tests for Chromedriver 131 (#12199)
* 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: fix selector warnings in WebdriverIO

Despite the [promises made](https://webdriver.io/docs/selectors#deep-selectors) by the WebdriverIO
team, we are still getting a lot of warnings and "falling back to pre-BIDI behavior" messages
when we attempt to access ShadowDOM contexts without the "pierce" (`>>>`) syntax.  So I've put
it back wherever it occurred and the system now uses the BIDI controllers correctly.

* web: update to Chromedriver 131 breaks a lot of stuff

This annoying bit of janitorial work cleans up the failure messages and resolution bugs
that arose when updating to the latest version of Chrome.  Keeping track of all the
weakness and breakage while the in-browser testing teams figure out how to live with
the ShadowDOM is just really time-consuming.
2024-12-02 08:19:51 -08:00
a65fb19489 website/integrations: add Aruba Orchestrator (#12220)
Co-authored-by: 4d62 <github-user@sdko.org>
Co-authored-by: jazzyj123 <76889039+jazzyj123@users.noreply.github.com>
Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-12-02 08:29:33 -06:00
dcbee92cd2 core: bump aws-cdk-lib from 2.167.1 to 2.171.1 (#12237)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.167.1 to 2.171.1.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.md)
- [Commits](https://github.com/aws/aws-cdk/compare/v2.167.1...v2.171.1)

---
updated-dependencies:
- dependency-name: aws-cdk-lib
  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-12-02 15:00:27 +02:00
dd0dc75951 website: bump aws-cdk from 2.167.1 to 2.171.1 in /website (#12241)
Bumps [aws-cdk](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk) from 2.167.1 to 2.171.1.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.md)
- [Commits](https://github.com/aws/aws-cdk/commits/v2.171.1/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  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-12-02 15:00:12 +02:00
02672e008c core, web: update translations (#12236)
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-12-02 14:56:05 +02:00
708105474c core: bump python-kadmin-rs from 0.2.0 to 0.3.0 (#12238)
Bumps [python-kadmin-rs](https://github.com/authentik-community/kadmin-rs) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/authentik-community/kadmin-rs/releases)
- [Commits](https://github.com/authentik-community/kadmin-rs/compare/python-kadmin-rs/version/0.2.0...python-kadmin-rs/version/0.3.0)

---
updated-dependencies:
- dependency-name: python-kadmin-rs
  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-12-02 14:55:46 +02:00
2d2fb635dd core: bump pytest from 8.3.3 to 8.3.4 (#12239)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.3 to 8.3.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.3...8.3.4)

---
updated-dependencies:
- dependency-name: pytest
  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-12-02 14:55:14 +02:00
dc3174529b core: bump drf-spectacular from 0.27.2 to 0.28.0 (#12240)
Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.27.2 to 0.28.0.
- [Release notes](https://github.com/tfranzel/drf-spectacular/releases)
- [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.27.2...0.28.0)

---
updated-dependencies:
- dependency-name: drf-spectacular
  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-12-02 14:55:02 +02:00
8a5adb78fb core, web: update translations (#12222)
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-12-01 23:49:32 +02:00
2f9ad00122 core: Bump ruff from 0.8.0 to 0.8.1 (#12224)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.0 to 0.8.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.8.0...0.8.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-12-01 23:49:21 +02:00
8534005936 core: Bump ua-parser from 0.18.0 to 1.0.0 (#12225)
Bumps [ua-parser](https://github.com/ua-parser/uap-python) from 0.18.0 to 1.0.0.
- [Release notes](https://github.com/ua-parser/uap-python/releases)
- [Commits](https://github.com/ua-parser/uap-python/compare/0.18.0...1.0.0)

---
updated-dependencies:
- dependency-name: ua-parser
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 23:49:12 +02:00
4bb6b23b9a core: Bump msgraph-sdk from 1.13.0 to 1.14.0 (#12226)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.13.0 to 1.14.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.13.0...v1.14.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-12-01 23:49:02 +02:00
3ef1ac2980 stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#12234)
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-12-01 23:48:19 +02:00
fda6054285 website/docs: install: add aws (#12082) 2024-12-01 15:43:14 +00:00
13b2543221 core: Bump pyjwt from 2.10.0 to 2.10.1 (#12217)
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.10.0...2.10.1)

---
updated-dependencies:
- dependency-name: pyjwt
  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-11-28 11:26:48 +01:00
87259c3c10 core: Bump fido2 from 1.1.3 to 1.2.0 (#12218)
Bumps [fido2](https://github.com/Yubico/python-fido2) from 1.1.3 to 1.2.0.
- [Release notes](https://github.com/Yubico/python-fido2/releases)
- [Changelog](https://github.com/Yubico/python-fido2/blob/main/NEWS)
- [Commits](https://github.com/Yubico/python-fido2/compare/1.1.3...1.2.0)

---
updated-dependencies:
- dependency-name: fido2
  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-11-28 11:26:36 +01:00
cd3a058a97 core: Bump cryptography from 43.0.3 to 44.0.0 (#12219)
Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.3 to 44.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/43.0.3...44.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-28 11:25:44 +01:00
f9e8138be3 providers/oauth2: allow m2m for JWKS without alg in keys (#12196)
* providers/oauth2: allow m2m for JWKS without alg in keys

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

* Update index.md

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

* Apply suggestions from code review

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

---------

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-11-27 19:01:40 +01:00
c05124c9dd translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#12210)
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-11-27 15:52:50 +01:00
90997efe29 translate: Updates for file web/xliff/en.xlf in fr (#12212)
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-11-27 15:48:22 +01:00
d69322ac68 website/docs: update procedurals for assigning roles to groups (#12198)
* update for dual-select

* add new dual-select unit and tweaks

* polishes

---------

Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-11-27 08:27:44 -06:00
3996bdac33 website: Bump prettier from 3.3.3 to 3.4.1 in /website (#12205)
* website: Bump prettier from 3.3.3 to 3.4.1 in /website

Bumps [prettier](https://github.com/prettier/prettier) from 3.3.3 to 3.4.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.4.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update formatting

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

* sigh

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

* disable flaky test

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-11-27 15:14:19 +01:00
6d2072a730 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12202)
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-11-27 13:15:52 +01:00
479242440e translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12201)
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-11-27 13:15:44 +01:00
7bba94a374 translate: Updates for file web/xliff/en.xlf in zh-Hans (#12203)
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-11-27 13:15:32 +01:00
7d47628d76 translate: Updates for file web/xliff/en.xlf in zh_CN (#12204)
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-11-27 13:15:29 +01:00
23a6fb959a core: Bump bandit from 1.7.10 to 1.8.0 (#12206)
Bumps [bandit](https://github.com/PyCQA/bandit) from 1.7.10 to 1.8.0.
- [Release notes](https://github.com/PyCQA/bandit/releases)
- [Commits](https://github.com/PyCQA/bandit/compare/1.7.10...1.8.0)

---
updated-dependencies:
- dependency-name: bandit
  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-11-27 13:15:04 +01:00
affcef3ee8 core: Bump pydantic from 2.10.1 to 2.10.2 (#12207)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.1 to 2.10.2.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.1...v2.10.2)

---
updated-dependencies:
- dependency-name: pydantic
  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-11-27 13:14:55 +01:00
27df0be5fa core: Bump selenium from 4.27.0 to 4.27.1 (#12208)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.27.0 to 4.27.1.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits)

---
updated-dependencies:
- dependency-name: selenium
  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-11-27 13:14:47 +01:00
694a65b4aa website/docs: fix missing CVE missing from sidebar (#12197)
* website/docs: fix missing cve in sidebar

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

* fix missing redirect

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-11-26 21:20:22 +01:00
1daa5315d7 website/docs: Add note about single group per role (#12169)
This change adds an admonition to document the fact that every role can only ever be assigned to a single group at the same time. Since this is surprising based on a traditional understanding of role-based models, I've decided to make this a `:::warning`.

I'm undecided on the best place for this information, but for now, decided on putting it into the context of the action that can fail: assigning a role to a group.

While this does not close the issue, it documents this behavior to at least address the "needs documentation" aspect of #10983 .

Signed-off-by: Zuri Klaschka <pklaschka@users.noreply.github.com>
2024-11-26 10:23:29 -06:00
709e413e46 website/docs: Fix documentation about attribute merging for indirect membership (#12168)
While for role memberships, it is true that they are only applied for _direct_ memberships, this does not appear to be the case for attributes (which is good as this also follows the "Hierarchy" system documented in the same file).

In terms of the implementation, this is the case due to the call to `all_groups()` in 3d5a189fa7/authentik/core/models.py (L312-L313), introduced in https://github.com/goauthentik/authentik/pull/6017. Looking through the files in there, it is clear that this line in the documentation is from before that point: 95e60a035d/website/docs/user-group/group.md (L15).

tl;dr: the documentation was correct before #6017, but is now out of date. This change fixes that.

Signed-off-by: Zuri Klaschka <pklaschka@users.noreply.github.com>
2024-11-26 09:51:01 -06:00
5e72ec9c0c root: support running authentik in subpath (#8675)
* initial subpath support

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

* make outpost compatible

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

* fix static files somewhat

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

* fix web interface

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

* fix most static stuff

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

* fix most web links

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

* fix websocket

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

* fix URL for static files

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

* format web

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

* add root redirect for subpath

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

* update docs

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

* set cookie path

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

* Update internal/config/struct.go

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

* fix sfe

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

* bump required version

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

* fix flow background

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

* fix lint and some more links

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

* format

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

* fix impersonate

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

* fix

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Signed-off-by: Jens L. <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-11-26 15:38:23 +01:00
ee15dbf671 docs: fix contribution link (#12189)
* 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.

* docs: fix link from project root to the Contributing documentation in our product.
2024-11-26 14:11:38 +01:00
4444779fcb core, web: update translations (#12190)
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-11-26 14:11:12 +01:00
48ddbc4283 core: Bump msgraph-sdk from 1.12.0 to 1.13.0 (#12191)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.12.0 to 1.13.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.12.0...v1.13.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-11-26 14:11:02 +01:00
bd92f9ab50 core: Bump selenium from 4.26.1 to 4.27.0 (#12192)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.26.1 to 4.27.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits/selenium-4.27.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-11-26 14:10:50 +01:00
6c1ad982a1 website/docs: Fix CSP syntax (#12124)
Fix CSP syntax

Scheme sources need to not have quotes https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#scheme-source

Signed-off-by: Felix Schäfer <felix.schaefer@tu-dortmund.de>
2024-11-25 18:58:44 +01:00
630e0e6bf2 ci: only mirror if secret is available (#12181)
* ci: only mirror if secret is available

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

* fix unrelated issues

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-11-25 14:59:07 +01:00
bebd4cd03f root: fix database ssl options not set correctly (#12180)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-11-25 14:56:05 +01:00
71b9b29a7d core, web: update translations (#12145)
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-11-25 14:32:41 +01:00
cc65fcd806 core: bump tornado from 6.4.1 to 6.4.2 (#12165)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 14:32:14 +01:00
9f82c87d2a website: bump the docusaurus group in /website with 9 updates (#12172)
Bumps the docusaurus group in /website with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@docusaurus/core](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus) | `3.6.2` | `3.6.3` |
| [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) | `3.6.2` | `3.6.3` |
| [@docusaurus/plugin-content-docs](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-content-docs) | `3.6.2` | `3.6.3` |
| [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) | `3.6.2` | `3.6.3` |
| [@docusaurus/theme-common](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-common) | `3.6.2` | `3.6.3` |
| [@docusaurus/theme-mermaid](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-theme-mermaid) | `3.6.2` | `3.6.3` |
| [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) | `3.6.2` | `3.6.3` |
| [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig) | `3.6.2` | `3.6.3` |
| [@docusaurus/types](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-types) | `3.6.2` | `3.6.3` |


Updates `@docusaurus/core` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus)

Updates `@docusaurus/plugin-client-redirects` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-plugin-client-redirects)

Updates `@docusaurus/plugin-content-docs` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-plugin-content-docs)

Updates `@docusaurus/preset-classic` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-preset-classic)

Updates `@docusaurus/theme-common` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-theme-common)

Updates `@docusaurus/theme-mermaid` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-theme-mermaid)

Updates `@docusaurus/module-type-aliases` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-module-type-aliases)

Updates `@docusaurus/tsconfig` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-tsconfig)

Updates `@docusaurus/types` from 3.6.2 to 3.6.3
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.6.3/packages/docusaurus-types)

---
updated-dependencies:
- dependency-name: "@docusaurus/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/plugin-content-docs"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/preset-classic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/theme-common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/theme-mermaid"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/module-type-aliases"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/tsconfig"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: docusaurus
- dependency-name: "@docusaurus/types"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: docusaurus
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 11:53:21 +01:00
0f76445ed7 website: bump typescript from 5.6.3 to 5.7.2 in /website (#12173)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.6.3 to 5.7.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.3...v5.7.2)

---
updated-dependencies:
- dependency-name: typescript
  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-11-25 11:53:10 +01:00
ab1e9a0cec ci: bump actions/checkout from 3 to 4 (#12174)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 11:53:00 +01:00
30fa8ee75f core: bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#12175)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  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-11-25 11:52:50 +01:00
ea9a596780 core: bump coverage from 7.6.7 to 7.6.8 (#12176)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.7 to 7.6.8.
- [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.7...7.6.8)

---
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-11-25 11:52:41 +01:00
ca34d39c16 core: bump ruff from 0.7.4 to 0.8.0 (#12177)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.4 to 0.8.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.7.4...0.8.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-11-25 11:52:32 +01:00
3d5a189fa7 ci: mirror repo to internal repo (#12160)
* don't push when on internal repo

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

* only run certain workflows on main repo

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

* add mirror

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

* how tf did a tab get in there

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

* ooops

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-11-22 18:26:56 +01:00
785403de18 core: bump goauthentik.io/api/v3 from 3.2024102.2 to 3.2024104.1 (#12149)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024102.2 to 3.2024104.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.2024102.2...v3.2024104.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-11-22 15:22:41 +01:00
1c4165a373 core: bump debugpy from 1.8.8 to 1.8.9 (#12150)
Bumps [debugpy](https://github.com/microsoft/debugpy) from 1.8.8 to 1.8.9.
- [Release notes](https://github.com/microsoft/debugpy/releases)
- [Commits](https://github.com/microsoft/debugpy/compare/v1.8.8...v1.8.9)

---
updated-dependencies:
- dependency-name: debugpy
  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-11-22 14:17:36 +01:00
bbd03b2b05 core: bump webauthn from 2.2.0 to 2.3.0 (#12151)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: webauthn
  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-11-22 14:17:28 +01:00
dd79aec5a6 core: bump pydantic from 2.10.0 to 2.10.1 (#12152)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.0...v2.10.1)

---
updated-dependencies:
- dependency-name: pydantic
  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-11-22 14:17:20 +01:00
3634ae3db9 translate: Updates for file web/xliff/en.xlf in zh_CN (#12156)
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-11-22 14:17:13 +01:00
12e1ee93ed translate: Updates for file web/xliff/en.xlf in zh-Hans (#12157)
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-11-22 14:16:52 +01:00
62aa3659b8 core: bump sentry-sdk from 2.18.0 to 2.19.0 (#12153)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.18.0 to 2.19.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.18.0...2.19.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-11-22 13:52:28 +01:00
23ec05a86c web: bump API Client version (#12147)
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-11-22 13:51:40 +01:00
520148bba4 root: Backport version change (#12146)
* release: 2024.10.3

* release: 2024.10.4
2024-11-22 01:51:30 +01:00
484 changed files with 28879 additions and 24788 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2024.10.2
current_version = 2024.10.5
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*))?
@ -30,3 +30,5 @@ optional_value = final
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:web/src/common/constants.ts]
[bumpversion:file:website/docs/install-config/install/aws/template.yaml]

View File

@ -11,9 +11,9 @@ inputs:
description: "Docker image arch"
outputs:
shouldBuild:
description: "Whether to build image or not"
value: ${{ steps.ev.outputs.shouldBuild }}
shouldPush:
description: "Whether to push the image or not"
value: ${{ steps.ev.outputs.shouldPush }}
sha:
description: "sha"

View File

@ -7,7 +7,14 @@ from time import time
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
should_build = str(len(os.environ.get("DOCKER_USERNAME", "")) > 0).lower()
# Decide if we should push the image or not
should_push = True
if len(os.environ.get("DOCKER_USERNAME", "")) < 1:
# Don't push if we don't have DOCKER_USERNAME, i.e. no secrets are available
should_push = False
if os.environ.get("GITHUB_REPOSITORY").lower() == "goauthentik/authentik-internal":
# Don't push on the internal repo
should_push = False
branch_name = os.environ["GITHUB_REF"]
if os.environ.get("GITHUB_HEAD_REF", "") != "":
@ -64,7 +71,7 @@ def get_attest_image_names(image_with_tags: list[str]):
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
print(f"shouldBuild={should_build}", file=_output)
print(f"shouldPush={str(should_push).lower()}", file=_output)
print(f"sha={sha}", file=_output)
print(f"version={version}", file=_output)
print(f"prerelease={prerelease}", file=_output)

View File

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

View File

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

46
.github/workflows/ci-aws-cfn.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: authentik-ci-aws-cfn
on:
push:
branches:
- main
- next
- version-*
pull_request:
branches:
- main
- version-*
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
check-changes-applied:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup authentik env
uses: ./.github/actions/setup
- uses: actions/setup-node@v4
with:
node-version-file: website/package.json
cache: "npm"
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: |
npm ci
- name: Check changes have been applied
run: |
poetry run make aws-cfn
git diff --exit-code
ci-aws-cfn-mark:
if: always()
needs:
- check-changes-applied
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

@ -209,6 +209,7 @@ jobs:
file: unittest.xml
token: ${{ secrets.CODECOV_TOKEN }}
ci-core-mark:
if: always()
needs:
- lint
- test-migrations
@ -218,7 +219,9 @@ jobs:
- test-e2e
runs-on: ubuntu-latest
steps:
- run: echo mark
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
build:
strategy:
fail-fast: false
@ -252,7 +255,7 @@ jobs:
image-name: ghcr.io/goauthentik/dev-server
image-arch: ${{ matrix.arch }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
@ -269,15 +272,15 @@ jobs:
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
tags: ${{ steps.ev.outputs.imageTags }}
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache
cache-to: ${{ steps.ev.outputs.shouldBuild == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max' || '' }}
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max' || '' }}
platforms: linux/${{ matrix.arch }}
- uses: actions/attest-build-provenance@v1
- uses: actions/attest-build-provenance@v2
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}
@ -303,7 +306,7 @@ jobs:
with:
image-name: ghcr.io/goauthentik/dev-server
- name: Comment on PR
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: ./.github/actions/comment-pr-instructions
with:
tag: ${{ steps.ev.outputs.imageMainTag }}

View File

@ -49,12 +49,15 @@ jobs:
run: |
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./...
ci-outpost-mark:
if: always()
needs:
- lint-golint
- test-unittest
runs-on: ubuntu-latest
steps:
- run: echo mark
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
build-container:
timeout-minutes: 120
needs:
@ -90,7 +93,7 @@ jobs:
with:
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
- name: Login to Container Registry
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
@ -104,16 +107,16 @@ jobs:
with:
tags: ${{ steps.ev.outputs.imageTags }}
file: ${{ matrix.type }}.Dockerfile
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: linux/amd64,linux/arm64
context: .
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
cache-to: ${{ steps.ev.outputs.shouldBuild == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@v1
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
- uses: actions/attest-build-provenance@v2
id: attest
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@ -61,12 +61,15 @@ jobs:
working-directory: web/
run: npm run build
ci-web-mark:
if: always()
needs:
- build
- lint
runs-on: ubuntu-latest
steps:
- run: echo mark
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
test:
needs:
- ci-web-mark

View File

@ -62,10 +62,13 @@ jobs:
working-directory: website/
run: npm run ${{ matrix.job }}
ci-website-mark:
if: always()
needs:
- lint
- test
- build
runs-on: ubuntu-latest
steps:
- run: echo mark
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

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

View File

@ -7,6 +7,7 @@ on:
jobs:
clean-ghcr:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
name: Delete old unused container images
runs-on: ubuntu-latest
steps:

View File

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

View File

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

View File

@ -55,7 +55,7 @@ jobs:
VERSION=${{ github.ref }}
tags: ${{ steps.ev.outputs.imageTags }}
platforms: linux/amd64,linux/arm64
- uses: actions/attest-build-provenance@v1
- uses: actions/attest-build-provenance@v2
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
@ -119,7 +119,7 @@ jobs:
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- uses: actions/attest-build-provenance@v1
- uses: actions/attest-build-provenance@v2
id: attest
with:
subject-name: ${{ steps.ev.outputs.attestImageNames }}
@ -169,6 +169,27 @@ jobs:
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }}
upload-aws-cfn-template:
permissions:
# Needed for AWS login
id-token: write
contents: read
needs:
- build-server
- build-outpost
env:
AWS_REGION: eu-central-1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
- name: Upload template
run: |
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
test-release:
needs:
- build-server

21
.github/workflows/repo-mirror.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: "authentik-repo-mirror"
on: [push, delete]
jobs:
to_internal:
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- if: ${{ env.MIRROR_KEY != '' }}
uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url:
git@github.com:goauthentik/authentik-internal.git
ssh_private_key:
${{ secrets.GH_MIRROR_KEY }}
env:
MIRROR_KEY: ${{ secrets.GH_MIRROR_KEY }}

View File

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

View File

@ -1 +1 @@
website/developer-docs/index.md
website/docs/developer-docs/index.md

View File

@ -5,7 +5,7 @@ PWD = $(shell pwd)
UID = $(shell id -u)
GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.npm_version)
PY_SOURCES = authentik tests scripts lifecycle .github
PY_SOURCES = authentik tests scripts lifecycle .github website/docs/install-config/install/aws
DOCKER_IMAGE ?= "authentik:test"
GEN_API_TS = "gen-ts-api"
@ -252,6 +252,9 @@ website-build:
website-watch: ## Build and watch the documentation website, updating automatically
cd website && npm run watch
aws-cfn:
cd website && npm run aws-cfn
#########################
## Docker
#########################

View File

@ -2,7 +2,7 @@ authentik takes security very seriously. We follow the rules of [responsible di
## Independent audits and pentests
In May/June of 2023 [Cure53](https://cure53.de) conducted an audit and pentest. The [results](https://cure53.de/pentest-report_authentik.pdf) are published on the [Cure53 website](https://cure53.de/#publications-2023). For more details about authentik's response to the findings of the audit refer to [2023-06 Cure53 Code audit](https://goauthentik.io/docs/security/2023-06-cure53).
We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specfic audits and pentests, refer to "Audits and Certificates" in our [Security documentation]](https://docs.goauthentik.io/docs/security).
## What authentik classifies as a CVE

View File

@ -2,7 +2,7 @@
from os import environ
__version__ = "2024.10.2"
__version__ = "2024.10.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -65,7 +65,12 @@ from authentik.lib.utils.reflection import get_apps
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
from authentik.providers.oauth2.models import (
AccessToken,
AuthorizationCode,
DeviceToken,
RefreshToken,
)
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.rbac.models import Role
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
@ -125,6 +130,7 @@ def excluded_models() -> list[type[Model]]:
MicrosoftEntraProviderGroup,
EndpointDevice,
EndpointDeviceConnection,
DeviceToken,
)

View File

@ -159,7 +159,7 @@ def blueprints_discovery(self: SystemTask, path: str | None = None):
check_blueprint_v1_file(blueprint)
count += 1
self.set_status(
TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": count})
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=count))
)

View File

@ -84,8 +84,8 @@ class CurrentBrandSerializer(PassiveSerializer):
matched_domain = CharField(source="domain")
branding_title = CharField()
branding_logo = CharField()
branding_favicon = CharField()
branding_logo = CharField(source="branding_logo_url")
branding_favicon = CharField(source="branding_favicon_url")
ui_footer_links = ListField(
child=FooterLinkSerializer(),
read_only=True,

View File

@ -25,5 +25,7 @@ class BrandMiddleware:
locale = brand.default_locale
if locale != "":
locale_to_set = locale
with override(locale_to_set):
return self.get_response(request)
if locale_to_set:
with override(locale_to_set):
return self.get_response(request)
return self.get_response(request)

View File

@ -10,6 +10,7 @@ from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.models import SerializerModel
LOGGER = get_logger()
@ -71,6 +72,18 @@ class Brand(SerializerModel):
)
attributes = models.JSONField(default=dict, blank=True)
def branding_logo_url(self) -> str:
"""Get branding_logo with the correct prefix"""
if self.branding_logo.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.branding_logo
return self.branding_logo
def branding_favicon_url(self) -> str:
"""Get branding_favicon with the correct prefix"""
if self.branding_favicon.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.branding_favicon
return self.branding_favicon
@property
def serializer(self) -> Serializer:
from authentik.brands.api import BrandSerializer

View File

@ -103,9 +103,6 @@ class GroupSerializer(ModelSerializer):
"users": {
"default": list,
},
# TODO: This field isn't unique on the database which is hard to backport
# hence we just validate the uniqueness here
"name": {"validators": [UniqueValidator(Group.objects.all())]},
}

View File

@ -42,8 +42,10 @@ class ImpersonateMiddleware:
# Ensure that the user is active, otherwise nothing will work
request.user.is_active = True
with override(locale_to_set):
return self.get_response(request)
if locale_to_set:
with override(locale_to_set):
return self.get_response(request)
return self.get_response(request)
class RequestIDMiddleware:

View File

@ -0,0 +1,21 @@
# Generated by Django 5.0.8 on 2024-08-08 12:09
from django.db import migrations, models
from authentik.lib.migrations import fallback_names
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0039_source_group_matching_mode_alter_group_name_and_more"),
]
operations = [
migrations.RunPython(fallback_names("authentik_core", "group", "name")),
migrations.AlterField(
model_name="group",
name="name",
field=models.TextField(unique=True, verbose_name="name"),
),
]

View File

@ -173,7 +173,7 @@ class Group(SerializerModel, AttributesMixin):
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(_("name"))
name = models.TextField(verbose_name=_("name"), unique=True)
is_superuser = models.BooleanField(
default=False, help_text=_("Users added to this group will be superusers.")
)

View File

@ -265,12 +265,7 @@ class SourceFlowManager:
if stages:
for stage in stages:
plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=flow.slug,
)
return plan.to_redirect(self.request, flow)
def handle_auth(
self,

View File

@ -9,6 +9,9 @@
versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}",
api: {
base: "{{ base_url }}",
},
};
window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %}

View File

@ -9,8 +9,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block head_before %}
<link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" />
<link rel="prefetch" href="{% static 'dist/assets/images/flow_background.jpg' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
@ -13,7 +13,7 @@
{% block head %}
<style>
:root {
--ak-flow-background: url("/static/dist/assets/images/flow_background.jpg");
--ak-flow-background: url("{% static 'dist/assets/images/flow_background.jpg' %}");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
@ -50,7 +50,7 @@
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo }}" alt="authentik Logo" />
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">

View File

@ -17,10 +17,8 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import (
SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN,
ToDefaultFlow,
)
from authentik.lib.utils.urls import redirect_with_qs
from authentik.stages.consent.stage import (
PLAN_CONTEXT_CONSENT_HEADER,
PLAN_CONTEXT_CONSENT_PERMISSIONS,
@ -58,8 +56,7 @@ class RedirectToAppLaunch(View):
except FlowNonApplicableException:
raise Http404 from None
plan.insert_stage(in_memory_stage(RedirectToAppStage))
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
return plan.to_redirect(request, flow)
class RedirectToAppStage(ChallengeStageView):

View File

@ -16,6 +16,7 @@ from authentik.api.v3.config import ConfigView
from authentik.brands.api import CurrentBrandSerializer
from authentik.brands.models import Brand
from authentik.core.models import UserTypes
from authentik.lib.config import CONFIG
from authentik.policies.denied import AccessDeniedResponse
@ -51,6 +52,7 @@ class InterfaceView(TemplateView):
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
kwargs["build"] = get_build_hash()
kwargs["url_kwargs"] = self.kwargs
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
return super().get_context_data(**kwargs)

View File

@ -85,5 +85,5 @@ def certificate_discovery(self: SystemTask):
if dirty:
cert.save()
self.set_status(
TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": discovered})
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=discovered))
)

View File

@ -6,6 +6,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
from django.urls import resolve
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.api.users import UserViewSet
from authentik.enterprise.api import LicenseViewSet
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import LicenseUsageStatus
@ -59,6 +60,9 @@ class EnterpriseMiddleware:
# Flow executor is mounted as an API path but explicitly allowed
if request.resolver_match._func_path == class_to_path(FlowExecutorView):
return True
# Always allow making changes to users, even in case the license has ben exceeded
if request.resolver_match._func_path == class_to_path(UserViewSet):
return True
# Only apply these restrictions to the API
if "authentik_api" not in request.resolver_match.app_names:
return True

View File

@ -6,8 +6,8 @@
<script src="{% versioned_script 'dist/enterprise/rac/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon }}">
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
{% include "base/header_js.html" %}
{% endblock %}

View File

@ -18,9 +18,7 @@ from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import RedirectStage
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.engine import PolicyEngine
@ -56,12 +54,7 @@ class RACStartView(EnterprisePolicyAccessView):
provider=self.provider,
)
)
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
request.GET,
flow_slug=self.provider.authorization_flow.slug,
)
return plan.to_redirect(request, self.provider.authorization_flow)
class RACInterface(InterfaceView):

View File

@ -4,7 +4,9 @@ from typing import Any
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.clickjacking import xframe_options_sameorigin
from googleapiclient.discovery import build
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
@ -26,6 +28,7 @@ HEADER_ACCESS_CHALLENGE_RESPONSE = "X-Verified-Access-Challenge-Response"
DEVICE_TRUST_VERIFIED_ACCESS = "VerifiedAccess"
@method_decorator(xframe_options_sameorigin, name="dispatch")
class GoogleChromeDeviceTrustConnector(View):
"""Google Chrome Device-trust connector based endpoint authenticator"""

View File

@ -215,3 +215,49 @@ class TestReadOnly(FlowTestCase):
{"detail": "Request denied due to expired/invalid license.", "code": "denied_license"},
)
self.assertEqual(response.status_code, 400)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_internal_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.get_external_user_count",
MagicMock(return_value=1000),
)
@patch(
"authentik.enterprise.license.LicenseKey.record_usage",
MagicMock(),
)
def test_manage_users(self):
"""Test that managing users is still possible"""
License.objects.create(key=generate_id())
usage = LicenseUsage.objects.create(
internal_user_count=100,
external_user_count=100,
status=LicenseUsageStatus.VALID,
)
usage.record_date = now() - timedelta(weeks=THRESHOLD_READ_ONLY_WEEKS + 1)
usage.save(update_fields=["record_date"])
admin = create_test_admin_user()
self.client.force_login(admin)
# Reading is always allowed
response = self.client.get(reverse("authentik_api:user-list"))
self.assertEqual(response.status_code, 200)
# Writing should also be allowed
response = self.client.patch(reverse("authentik_api:user-detail", kwargs={"pk": admin.pk}))
self.assertEqual(response.status_code, 200)

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Token
from authentik.core.types import UserSettingSerializer
from authentik.flows.challenge import FlowLayout
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.models import PolicyBindingModel
@ -177,9 +178,13 @@ class Flow(SerializerModel, PolicyBindingModel):
"""Get the URL to the background image. If the name is /static or starts with http
it is returned as-is"""
if not self.background:
return "/static/dist/assets/images/flow_background.jpg"
if self.background.name.startswith("http") or self.background.name.startswith("/static"):
return (
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg"
)
if self.background.name.startswith("http"):
return self.background.name
if self.background.name.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.background.name
return self.background.url
stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True)

View File

@ -1,10 +1,10 @@
"""Flows Planner"""
from dataclasses import dataclass, field
from typing import Any
from typing import TYPE_CHECKING, Any
from django.core.cache import cache
from django.http import HttpRequest
from django.http import HttpRequest, HttpResponse
from sentry_sdk import start_span
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
@ -23,10 +23,15 @@ from authentik.flows.models import (
in_memory_stage,
)
from authentik.lib.config import CONFIG
from authentik.lib.utils.urls import redirect_with_qs
from authentik.outposts.models import Outpost
from authentik.policies.engine import PolicyEngine
from authentik.root.middleware import ClientIPMiddleware
if TYPE_CHECKING:
from authentik.flows.stage import StageView
LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso"
@ -110,6 +115,54 @@ class FlowPlan:
"""Check if there are any stages left in this plan"""
return len(self.markers) + len(self.bindings) > 0
def requires_flow_executor(
self,
allowed_silent_types: list["StageView"] | None = None,
):
# Check if we actually need to show the Flow executor, or if we can jump straight to the end
found_unskippable = True
if allowed_silent_types:
LOGGER.debug("Checking if we can skip the flow executor...")
# Policies applied to the flow have already been evaluated, so we're checking for stages
# allow-listed or bindings that require a policy re-eval
found_unskippable = False
for binding, marker in zip(self.bindings, self.markers, strict=True):
if binding.stage.view not in allowed_silent_types:
found_unskippable = True
if marker and isinstance(marker, ReevaluateMarker):
found_unskippable = True
LOGGER.debug("Required flow executor status", status=found_unskippable)
return found_unskippable
def to_redirect(
self,
request: HttpRequest,
flow: Flow,
allowed_silent_types: list["StageView"] | None = None,
) -> HttpResponse:
"""Redirect to the flow executor for this flow plan"""
from authentik.flows.views.executor import (
SESSION_KEY_PLAN,
FlowExecutorView,
)
request.session[SESSION_KEY_PLAN] = self
requires_flow_executor = self.requires_flow_executor(allowed_silent_types)
if not requires_flow_executor:
# No unskippable stages found, so we can directly return the response of the last stage
final_stage: type[StageView] = self.bindings[-1].stage.view
temp_exec = FlowExecutorView(flow=flow, request=request, plan=self)
temp_exec.current_stage = self.bindings[-1].stage
stage = final_stage(request=request, executor=temp_exec)
return stage.dispatch(request)
return redirect_with_qs(
"authentik_core:if-flow",
request.GET,
flow_slug=flow.slug,
)
class FlowPlanner:
"""Execute all policies to plan out a flat list of all Stages

View File

@ -2,6 +2,7 @@
from typing import TYPE_CHECKING
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from django.http.request import QueryDict
@ -224,6 +225,14 @@ class ChallengeStageView(StageView):
full_errors[field].append(field_error)
challenge_response.initial_data["response_errors"] = full_errors
if not challenge_response.is_valid():
if settings.TEST:
raise StageInvalidException(
(
f"Invalid challenge response: \n\t{challenge_response.errors}"
f"\n\nValidated data:\n\t {challenge_response.data}"
f"\n\nInitial data:\n\t {challenge_response.initial_data}"
),
)
self.logger.error(
"f(ch): invalid challenge response",
errors=challenge_response.errors,

View File

@ -9,8 +9,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">

View File

@ -5,6 +5,8 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import cache
from django.http import HttpRequest
from django.shortcuts import redirect
from django.test import RequestFactory, TestCase
from django.urls import reverse
from guardian.shortcuts import get_anonymous_user
@ -14,8 +16,14 @@ from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
from authentik.flows.models import (
FlowAuthenticationRequirement,
FlowDesignation,
FlowStageBinding,
in_memory_stage,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.flows.stage import StageView
from authentik.lib.tests.utils import dummy_get_response
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
@ -211,3 +219,99 @@ class TestFlowPlanner(TestCase):
self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
def test_to_redirect(self):
"""Test to_redirect and skipping the flow executor"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.NONE
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
request.user = AnonymousUser()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
plan = planner.plan(request)
self.assertTrue(plan.requires_flow_executor())
self.assertEqual(
plan.to_redirect(request, flow).url,
reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}),
)
def test_to_redirect_skip_simple(self):
"""Test to_redirect and skipping the flow executor"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.NONE
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
request.user = AnonymousUser()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
plan = planner.plan(request)
class TStageView(StageView):
def dispatch(self, request: HttpRequest, *args, **kwargs):
return redirect("https://authentik.company")
plan.append_stage(in_memory_stage(TStageView))
self.assertFalse(plan.requires_flow_executor(allowed_silent_types=[TStageView]))
self.assertEqual(
plan.to_redirect(request, flow, allowed_silent_types=[TStageView]).url,
"https://authentik.company",
)
def test_to_redirect_skip_stage(self):
"""Test to_redirect and skipping the flow executor
(with a stage bound that cannot be skipped)"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.NONE
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
)
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
request.user = AnonymousUser()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
plan = planner.plan(request)
class TStageView(StageView):
def dispatch(self, request: HttpRequest, *args, **kwargs):
return redirect("https://authentik.company")
plan.append_stage(in_memory_stage(TStageView))
self.assertTrue(plan.requires_flow_executor(allowed_silent_types=[TStageView]))
def test_to_redirect_skip_policies(self):
"""Test to_redirect and skipping the flow executor
(with a marker on the stage view type that can be skipped)
Note that this is not actually used anywhere in the code, all stages that are dynamically
added are statically added"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.NONE
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
request.user = AnonymousUser()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
plan = planner.plan(request)
class TStageView(StageView):
def dispatch(self, request: HttpRequest, *args, **kwargs):
return redirect("https://authentik.company")
plan.append_stage(in_memory_stage(TStageView), ReevaluateMarker(None))
self.assertTrue(plan.requires_flow_executor(allowed_silent_types=[TStageView]))

View File

@ -597,9 +597,4 @@ class ConfigureFlowInitView(LoginRequiredMixin, View):
except FlowNonApplicableException:
LOGGER.warning("Flow not applicable to user")
raise Http404 from None
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=stage.configure_flow.slug,
)
return plan.to_redirect(request, stage.configure_flow)

View File

@ -135,6 +135,7 @@ web:
# No default here as it's set dynamically
# workers: 2
threads: 4
path: /
worker:
concurrency: 2

View File

@ -36,6 +36,7 @@ from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import get_env
LOGGER = get_logger()
_root_path = CONFIG.get("web.path", "/")
class SentryIgnoredException(Exception):
@ -90,7 +91,7 @@ def traces_sampler(sampling_context: dict) -> float:
path = sampling_context.get("asgi_scope", {}).get("path", "")
_type = sampling_context.get("asgi_scope", {}).get("type", "")
# Ignore all healthcheck routes
if path.startswith("/-/health") or path.startswith("/-/metrics"):
if path.startswith(f"{_root_path}-/health") or path.startswith(f"{_root_path}-/metrics"):
return 0
if _type == "websocket":
return 0

View File

@ -82,7 +82,7 @@ class SyncTasks:
return
try:
for page in users_paginator.page_range:
messages.append(_("Syncing page %(page)d of users" % {"page": page}))
messages.append(_("Syncing page {page} of users".format(page=page)))
for msg in sync_objects.apply_async(
args=(class_to_path(User), page, provider_pk),
time_limit=PAGE_TIMEOUT,
@ -90,7 +90,7 @@ class SyncTasks:
).get():
messages.append(LogEvent(**msg))
for page in groups_paginator.page_range:
messages.append(_("Syncing page %(page)d of groups" % {"page": page}))
messages.append(_("Syncing page {page} of groups".format(page=page)))
for msg in sync_objects.apply_async(
args=(class_to_path(Group), page, provider_pk),
time_limit=PAGE_TIMEOUT,

View File

@ -43,8 +43,9 @@ class PasswordExpiryPolicy(Policy):
request.user.set_unusable_password()
request.user.save()
message = _(
"Password expired %(days)d days ago. Please update your password."
% {"days": days_since_expiry}
"Password expired {days} days ago. Please update your password.".format(
days=days_since_expiry
)
)
return PolicyResult(False, message)
return PolicyResult(False, _("Password has expired."))

View File

@ -135,7 +135,7 @@ class PasswordPolicy(Policy):
LOGGER.debug("got hibp result", count=final_count, hash=pw_hash[:5])
if final_count > self.hibp_allowed_count:
LOGGER.debug("password failed", check="hibp", count=final_count)
message = _("Password exists on %(count)d online lists." % {"count": final_count})
message = _("Password exists on {count} online lists.".format(count=final_count))
return PolicyResult(False, message)
return PolicyResult(True)

View File

@ -73,7 +73,8 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"sub_mode",
"property_mappings",
"issuer_mode",
"jwks_sources",
"jwt_federation_sources",
"jwt_federation_providers",
]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0.9 on 2024-11-22 14:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0024_remove_oauth2provider_redirect_uris_and_more"),
]
operations = [
migrations.RenameField(
model_name="oauth2provider",
old_name="jwks_sources",
new_name="jwt_federation_sources",
),
migrations.AddField(
model_name="oauth2provider",
name="jwt_federation_providers",
field=models.ManyToManyField(
blank=True, default=None, to="authentik_providers_oauth2.oauth2provider"
),
),
]

View File

@ -244,7 +244,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
related_name="oauth2provider_encryption_key_set",
)
jwks_sources = models.ManyToManyField(
jwt_federation_sources = models.ManyToManyField(
OAuthSource,
verbose_name=_(
"Any JWT signed by the JWK of the selected source can be used to authenticate."
@ -253,6 +253,7 @@ class OAuth2Provider(WebfingerProvider, Provider):
default=None,
blank=True,
)
jwt_federation_providers = models.ManyToManyField("OAuth2Provider", blank=True, default=None)
@cached_property
def jwt_key(self) -> tuple[str | PrivateKeyTypes, str]:

View File

@ -311,7 +311,7 @@ class TestAuthorize(OAuthTestCase):
user = create_test_admin_user()
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -320,16 +320,10 @@ class TestAuthorize(OAuthTestCase):
"redirect_uri": "foo://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": f"foo://localhost?code={code.code}&state={state}",
},
self.assertEqual(
response.url,
f"foo://localhost?code={code.code}&state={state}",
)
self.assertAlmostEqual(
code.expires.timestamp() - now().timestamp(),
@ -377,7 +371,7 @@ class TestAuthorize(OAuthTestCase):
),
):
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
@ -388,22 +382,16 @@ class TestAuthorize(OAuthTestCase):
"nonce": generate_id(),
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
token: AccessToken = AccessToken.objects.filter(user=user).first()
expires = timedelta_from_string(provider.access_token_validity).total_seconds()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": (
f"http://localhost#access_token={token.token}"
f"&id_token={provider.encode(token.id_token.to_dict())}"
f"&token_type={TOKEN_TYPE}"
f"&expires_in={int(expires)}&state={state}"
),
},
self.assertEqual(
response.url,
(
f"http://localhost#access_token={token.token}"
f"&id_token={provider.encode(token.id_token.to_dict())}"
f"&token_type={TOKEN_TYPE}"
f"&expires_in={int(expires)}&state={state}"
),
)
jwt = self.validate_jwt(token, provider)
self.assertEqual(jwt["amr"], ["pwd"])
@ -455,7 +443,7 @@ class TestAuthorize(OAuthTestCase):
),
):
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
@ -466,10 +454,7 @@ class TestAuthorize(OAuthTestCase):
"nonce": generate_id(),
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.status_code, 302)
token: AccessToken = AccessToken.objects.filter(user=user).first()
expires = timedelta_from_string(provider.access_token_validity).total_seconds()
jwt = self.validate_jwe(token, provider)
@ -506,7 +491,7 @@ class TestAuthorize(OAuthTestCase):
),
):
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -518,16 +503,10 @@ class TestAuthorize(OAuthTestCase):
"nonce": generate_id(),
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": (f"http://localhost#code={code.code}" f"&state={state}"),
},
self.assertEqual(
response.url,
f"http://localhost#code={code.code}&state={state}",
)
self.assertAlmostEqual(
code.expires.timestamp() - now().timestamp(),

View File

@ -0,0 +1,228 @@
"""Test token view"""
from datetime import datetime, timedelta
from json import loads
from django.test import RequestFactory
from django.urls import reverse
from django.utils.timezone import now
from jwt import decode
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group
from authentik.core.tests.utils import create_test_cert, create_test_flow, create_test_user
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
GRANT_TYPE_CLIENT_CREDENTIALS,
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
TOKEN_TYPE,
)
from authentik.providers.oauth2.models import (
AccessToken,
OAuth2Provider,
RedirectURI,
RedirectURIMatchingMode,
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestTokenClientCredentialsJWTProvider(OAuthTestCase):
"""Test token (client_credentials, with JWT) view"""
@apply_blueprint("system/providers-oauth2.yaml")
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.other_cert = create_test_cert()
self.cert = create_test_cert()
self.other_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
signing_key=self.other_cert,
)
self.other_provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.other_provider
)
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
authorization_flow=create_test_flow(),
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
signing_key=self.cert,
)
self.provider.jwt_federation_providers.add(self.other_provider)
self.provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
def test_invalid_type(self):
"""test invalid type"""
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "foo",
"client_assertion": "foo.bar",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_jwt(self):
"""test invalid JWT"""
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": "foo.bar",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_signature(self):
"""test invalid JWT"""
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token + "foo",
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_expired(self):
"""test invalid JWT"""
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() - timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_no_app(self):
"""test invalid JWT"""
self.app.provider = None
self.app.save()
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_invalid_access_denied(self):
"""test invalid JWT"""
group = Group.objects.create(name="foo")
PolicyBinding.objects.create(
group=group,
target=self.app,
order=0,
)
token = self.provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 400)
body = loads(response.content.decode())
self.assertEqual(body["error"], "invalid_grant")
def test_successful(self):
"""test successful"""
user = create_test_user()
token = self.other_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
}
)
AccessToken.objects.create(
provider=self.other_provider,
token=token,
user=user,
auth_time=now(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
{
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
"client_id": self.provider.client_id,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": token,
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(body["token_type"], TOKEN_TYPE)
_, alg = self.provider.jwt_key
jwt = decode(
body["access_token"],
key=self.provider.signing_key.public_key,
algorithms=[alg],
audience=self.provider.client_id,
)
self.assertEqual(jwt["given_name"], user.name)
self.assertEqual(jwt["preferred_username"], user.username)

View File

@ -37,9 +37,16 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.other_cert = create_test_cert()
# Provider used as a helper to sign JWTs with the same key as the OAuth source has
self.helper_provider = OAuth2Provider.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
signing_key=self.other_cert,
)
self.cert = create_test_cert()
jwk = JWKSView().get_jwk_for_key(self.cert, "sig")
jwk = JWKSView().get_jwk_for_key(self.other_cert, "sig")
self.source: OAuthSource = OAuthSource.objects.create(
name=generate_id(),
slug=generate_id(),
@ -62,7 +69,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
redirect_uris=[RedirectURI(RedirectURIMatchingMode.STRICT, "http://testserver")],
signing_key=self.cert,
)
self.provider.jwks_sources.add(self.source)
self.provider.jwt_federation_sources.add(self.source)
self.provider.property_mappings.set(ScopeMapping.objects.all())
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
@ -100,7 +107,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_invalid_signature(self):
"""test invalid JWT"""
token = self.provider.encode(
token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@ -122,7 +129,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_invalid_expired(self):
"""test invalid JWT"""
token = self.provider.encode(
token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() - timedelta(hours=2),
@ -146,7 +153,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
"""test invalid JWT"""
self.app.provider = None
self.app.save()
token = self.provider.encode(
token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@ -174,7 +181,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
target=self.app,
order=0,
)
token = self.provider.encode(
token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),
@ -196,7 +203,7 @@ class TestTokenClientCredentialsJWTSource(OAuthTestCase):
def test_successful(self):
"""test successful"""
token = self.provider.encode(
token = self.helper_provider.encode(
{
"sub": "foo",
"exp": datetime.now() + timedelta(hours=2),

View File

@ -45,7 +45,7 @@ class TestTokenPKCE(OAuthTestCase):
challenge = generate_id()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -56,16 +56,10 @@ class TestTokenPKCE(OAuthTestCase):
"code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": f"foo://localhost?code={code.code}&state={state}",
},
self.assertEqual(
response.url,
f"foo://localhost?code={code.code}&state={state}",
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -107,7 +101,7 @@ class TestTokenPKCE(OAuthTestCase):
self.client.force_login(user)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -118,16 +112,10 @@ class TestTokenPKCE(OAuthTestCase):
# "code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": f"foo://localhost?code={code.code}&state={state}",
},
self.assertEqual(
response.url,
f"foo://localhost?code={code.code}&state={state}",
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -174,7 +162,7 @@ class TestTokenPKCE(OAuthTestCase):
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -185,16 +173,10 @@ class TestTokenPKCE(OAuthTestCase):
"code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": f"foo://localhost?code={code.code}&state={state}",
},
self.assertEqual(
response.url,
f"foo://localhost?code={code.code}&state={state}",
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -225,7 +207,7 @@ class TestTokenPKCE(OAuthTestCase):
verifier = generate_id()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
@ -235,16 +217,10 @@ class TestTokenPKCE(OAuthTestCase):
"code_challenge": verifier,
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"to": f"foo://localhost?code={code.code}&state={state}",
},
self.assertEqual(
response.url,
f"foo://localhost?code={code.code}&state={state}",
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),

View File

@ -27,9 +27,7 @@ from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.stage import StageView
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.policies.types import PolicyRequest
from authentik.policies.views import PolicyAccessView, RequestValidationError
@ -454,11 +452,16 @@ class AuthorizationFlowInitView(PolicyAccessView):
plan.append_stage(in_memory_stage(OAuthFulfillmentStage))
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=self.provider.authorization_flow.slug,
return plan.to_redirect(
self.request,
self.provider.authorization_flow,
# We can only skip the flow executor and directly go to the final redirect URL if
# we can submit the data to the RP via URL
allowed_silent_types=(
[OAuthFulfillmentStage]
if self.params.response_mode in [ResponseMode.QUERY, ResponseMode.FRAGMENT]
else []
),
)

View File

@ -16,7 +16,6 @@ from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.views import PolicyAccessView
from authentik.providers.oauth2.models import DeviceToken
from authentik.providers.oauth2.views.device_finish import (
@ -73,12 +72,7 @@ class CodeValidatorView(PolicyAccessView):
LOGGER.warning("Flow not applicable to user")
return None
plan.insert_stage(in_memory_stage(OAuthDeviceCodeFinishStage))
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
request.GET,
flow_slug=self.token.provider.authorization_flow.slug,
)
return plan.to_redirect(self.request, self.token.provider.authorization_flow)
class DeviceEntryView(PolicyAccessView):
@ -109,11 +103,7 @@ class DeviceEntryView(PolicyAccessView):
plan.append_stage(in_memory_stage(OAuthDeviceCodeStage))
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=device_flow.slug,
)
return plan.to_redirect(self.request, device_flow)
class OAuthDeviceCodeChallenge(Challenge):
@ -137,7 +127,7 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
class OAuthDeviceCodeStage(ChallengeStageView):
"""Flow challenge for users to enter device codes"""
"""Flow challenge for users to enter device code"""
response_class = OAuthDeviceCodeChallengeResponse

View File

@ -7,8 +7,6 @@ from authentik.core.models import Application
from authentik.flows.models import Flow, in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import SessionEndStage
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.views import PolicyAccessView
@ -37,9 +35,4 @@ class EndSessionView(PolicyAccessView):
},
)
plan.insert_stage(in_memory_stage(SessionEndStage))
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=self.flow.slug,
)
return plan.to_redirect(self.request, self.flow)

View File

@ -362,23 +362,9 @@ class TokenParams:
},
).from_http(request, user=user)
def __post_init_client_credentials_jwt(self, request: HttpRequest):
assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
raise TokenError("invalid_grant")
client_secret = request.POST.get("client_secret", None)
assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
if not assertion:
LOGGER.warning("Missing client assertion")
raise TokenError("invalid_grant")
token = None
source: OAuthSource | None = None
parsed_key: PyJWK | None = None
def __validate_jwt_from_source(
self, assertion: str
) -> tuple[dict, OAuthSource] | tuple[None, None]:
# Fully decode the JWT without verifying the signature, so we can get access to
# the header.
# Get the Key ID from the header, and use that to optimise our source query to only find
@ -393,19 +379,23 @@ class TokenParams:
LOGGER.warning("failed to parse JWT for kid lookup", exc=exc)
raise TokenError("invalid_grant") from None
expected_kid = decode_unvalidated["header"]["kid"]
for source in self.provider.jwks_sources.filter(
fallback_alg = decode_unvalidated["header"]["alg"]
token = source = None
for source in self.provider.jwt_federation_sources.filter(
oidc_jwks__keys__contains=[{"kid": expected_kid}]
):
LOGGER.debug("verifying JWT with source", source=source.slug)
keys = source.oidc_jwks.get("keys", [])
for key in keys:
if key.get("kid") and key.get("kid") != expected_kid:
continue
LOGGER.debug("verifying JWT with key", source=source.slug, key=key.get("kid"))
try:
parsed_key = PyJWK.from_dict(key)
parsed_key = PyJWK.from_dict(key).key
token = decode(
assertion,
parsed_key.key,
algorithms=[key.get("alg")],
parsed_key,
algorithms=[key.get("alg")] if "alg" in key else [fallback_alg],
options={
"verify_aud": False,
},
@ -414,13 +404,61 @@ class TokenParams:
# and not a public key
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, source=source.slug)
if token:
LOGGER.info("successfully verified JWT with source", source=source.slug)
return token, source
def __validate_jwt_from_provider(
self, assertion: str
) -> tuple[dict, OAuth2Provider] | tuple[None, None]:
token = provider = _key = None
federated_token = AccessToken.objects.filter(
token=assertion, provider__in=self.provider.jwt_federation_providers.all()
).first()
if federated_token:
_key, _alg = federated_token.provider.jwt_key
try:
token = decode(
assertion,
_key.public_key(),
algorithms=[_alg],
options={
"verify_aud": False,
},
)
provider = federated_token.provider
self.user = federated_token.user
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning(
"failed to verify JWT", exc=exc, provider=federated_token.provider.name
)
if token:
LOGGER.info("successfully verified JWT with provider", provider=provider.name)
return token, provider
def __post_init_client_credentials_jwt(self, request: HttpRequest):
assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
LOGGER.warning("Invalid assertion type", assertion_type=assertion_type)
raise TokenError("invalid_grant")
client_secret = request.POST.get("client_secret", None)
assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
if not assertion:
LOGGER.warning("Missing client assertion")
raise TokenError("invalid_grant")
source = provider = None
token, source = self.__validate_jwt_from_source(assertion)
if not token:
token, provider = self.__validate_jwt_from_provider(assertion)
if not token:
LOGGER.warning("No token could be verified")
raise TokenError("invalid_grant")
LOGGER.info("successfully verified JWT with source", source=source.slug)
if "exp" in token:
exp = datetime.fromtimestamp(token["exp"])
# Non-timezone aware check since we assume `exp` is in UTC
@ -434,15 +472,16 @@ class TokenParams:
raise TokenError("invalid_grant")
self.__check_policy_access(app, request, oauth_jwt=token)
self.__create_user_from_jwt(token, app, source)
if not provider:
self.__create_user_from_jwt(token, app, source)
method_args = {
"jwt": token,
}
if source:
method_args["source"] = source
if parsed_key:
method_args["jwk_id"] = parsed_key.key_id
if provider:
method_args["provider"] = provider
Event.new(
action=EventAction.LOGIN,
**{

View File

@ -94,7 +94,8 @@ class ProxyProviderSerializer(ProviderSerializer):
"intercept_header_auth",
"redirect_uris",
"cookie_domain",
"jwks_sources",
"jwt_federation_sources",
"jwt_federation_providers",
"access_token_validity",
"refresh_token_validity",
"outpost_set",

View File

@ -13,8 +13,6 @@ from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow, in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import SessionEndStage
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.policies.views import PolicyAccessView
from authentik.providers.saml.exceptions import CannotHandleAssertion
@ -64,12 +62,7 @@ class SAMLSLOView(PolicyAccessView):
},
)
plan.insert_stage(in_memory_stage(SessionEndStage))
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=self.flow.slug,
)
return plan.to_redirect(self.request, self.flow)
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""GET and POST use the same handler, but we can't

View File

@ -13,12 +13,11 @@ from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.views.executor import SESSION_KEY_PLAN, SESSION_KEY_POST
from authentik.lib.utils.urls import redirect_with_qs
from authentik.flows.views.executor import SESSION_KEY_POST
from authentik.lib.views import bad_request_message
from authentik.policies.views import PolicyAccessView
from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLProvider
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
from authentik.providers.saml.views.flows import (
REQUEST_KEY_RELAY_STATE,
@ -74,11 +73,12 @@ class SAMLSSOView(PolicyAccessView):
except FlowNonApplicableException:
raise Http404 from None
plan.append_stage(in_memory_stage(SAMLFlowFinalView))
request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
request.GET,
flow_slug=self.provider.authorization_flow.slug,
return plan.to_redirect(
request,
self.provider.authorization_flow,
allowed_silent_types=(
[SAMLFlowFinalView] if self.provider.sp_binding in [SAMLBindings.REDIRECT] else []
),
)
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:

View File

@ -32,13 +32,14 @@ LOGIN_URL = "authentik_flows:default-authentication"
# Custom user model
AUTH_USER_MODEL = "authentik_core.User"
CSRF_COOKIE_PATH = LANGUAGE_COOKIE_PATH = SESSION_COOKIE_PATH = CONFIG.get("web.path", "/")
CSRF_COOKIE_NAME = "authentik_csrf"
CSRF_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF"
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",
@ -304,10 +305,12 @@ DATABASES = {
"USER": CONFIG.get("postgresql.user"),
"PASSWORD": CONFIG.get("postgresql.password"),
"PORT": CONFIG.get("postgresql.port"),
"SSLMODE": CONFIG.get("postgresql.sslmode"),
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
"SSLCERT": CONFIG.get("postgresql.sslcert"),
"SSLKEY": CONFIG.get("postgresql.sslkey"),
"OPTIONS": {
"sslmode": CONFIG.get("postgresql.sslmode"),
"sslrootcert": CONFIG.get("postgresql.sslrootcert"),
"sslcert": CONFIG.get("postgresql.sslcert"),
"sslkey": CONFIG.get("postgresql.sslkey"),
},
"TEST": {
"NAME": CONFIG.get("postgresql.test.name"),
},
@ -425,7 +428,7 @@ if _ERROR_REPORTING:
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATICFILES_DIRS = [BASE_DIR / Path("web")]
STATIC_URL = "/static/"
STATIC_URL = CONFIG.get("web.path", "/") + "static/"
STORAGES = {
"staticfiles": {

View File

@ -4,6 +4,7 @@ from django.urls import include, path
from structlog.stdlib import get_logger
from authentik.core.views import error
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_apps
from authentik.root.monitoring import LiveView, MetricsView, ReadyView
@ -14,7 +15,7 @@ handler403 = error.ForbiddenView.as_view()
handler404 = error.NotFoundView.as_view()
handler500 = error.ServerErrorView.as_view()
urlpatterns = []
_urlpatterns = []
for _authentik_app in get_apps():
mountpoints = None
@ -35,7 +36,7 @@ for _authentik_app in get_apps():
namespace=namespace,
),
)
urlpatterns.append(_path)
_urlpatterns.append(_path)
LOGGER.debug(
"Mounted URLs",
app_name=_authentik_app.name,
@ -43,8 +44,10 @@ for _authentik_app in get_apps():
namespace=namespace,
)
urlpatterns += [
_urlpatterns += [
path("-/metrics/", MetricsView.as_view(), name="metrics"),
path("-/health/live/", LiveView.as_view(), name="health-live"),
path("-/health/ready/", ReadyView.as_view(), name="health-ready"),
]
urlpatterns = [path(CONFIG.get("web.path", "/")[1:], include(_urlpatterns))]

View File

@ -2,13 +2,16 @@
from importlib import import_module
from channels.routing import URLRouter
from django.urls import path
from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_apps
LOGGER = get_logger()
websocket_urlpatterns = []
_websocket_urlpatterns = []
for _authentik_app in get_apps():
try:
api_urls = import_module(f"{_authentik_app.name}.urls")
@ -17,8 +20,15 @@ for _authentik_app in get_apps():
if not hasattr(api_urls, "websocket_urlpatterns"):
continue
urls: list = api_urls.websocket_urlpatterns
websocket_urlpatterns.extend(urls)
_websocket_urlpatterns.extend(urls)
LOGGER.debug(
"Mounted Websocket URLs",
app_name=_authentik_app.name,
)
websocket_urlpatterns = [
path(
CONFIG.get("web.path", "/")[1:],
URLRouter(_websocket_urlpatterns),
),
]

View File

@ -45,8 +45,10 @@ def kerberos_sync_password(sender, user: User, password: str, **_):
continue
with Krb5ConfContext(source):
try:
source.connection().getprinc(user_source_connection.identifier).change_password(
password
kadm = source.connection()
kadm.get_principal(user_source_connection.identifier).change_password(
kadm,
password,
)
except PyKAdminException as exc:
LOGGER.warning("failed to set Kerberos password", exc=exc, source=source)

View File

@ -31,8 +31,7 @@ from authentik.flows.planner import (
FlowPlanner,
)
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
@ -89,12 +88,7 @@ class InitiateView(View):
raise Http404 from None
for stage in stages_to_append:
plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=source.pre_authentication_flow.slug,
)
return plan.to_redirect(self.request, source.pre_authentication_flow)
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
"""Replies with an XHTML SSO Request."""

View File

@ -332,7 +332,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
serializer = SelectableStageSerializer(
data={
"pk": stage.pk,
"name": getattr(stage, "friendly_name", stage.name),
"name": getattr(stage, "friendly_name", stage.name) or stage.name,
"verbose_name": str(stage._meta.verbose_name)
.replace("Setup Stage", "")
.strip(),

View File

@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
from django.test.client import RequestFactory
from django.urls.base import reverse
from django.utils.timezone import now
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowDesignation, FlowStageBinding, NotConfiguredAction
@ -13,6 +14,7 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage, TOTPDigits
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_validate.stage import PLAN_CONTEXT_DEVICE_CHALLENGES
@ -76,8 +78,8 @@ class AuthenticatorValidateStageTests(FlowTestCase):
conf_stage = AuthenticatorStaticStage.objects.create(
name=generate_id(),
)
conf_stage2 = AuthenticatorStaticStage.objects.create(
name=generate_id(),
conf_stage2 = AuthenticatorTOTPStage.objects.create(
name=generate_id(), digits=TOTPDigits.SIX
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
@ -153,10 +155,14 @@ class AuthenticatorValidateStageTests(FlowTestCase):
{
"device_class": "static",
"device_uid": "1",
"challenge": {},
"last_used": now(),
},
{
"device_class": "totp",
"device_uid": "2",
"challenge": {},
"last_used": now(),
},
]
session[SESSION_KEY_PLAN] = plan

View File

@ -127,19 +127,19 @@
"icon_dark":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo=",
"icon_light":"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwODAgMTA4MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxuczpzZXJpZj0iaHR0cDovL3d3dy5zZXJpZi5jb20vIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjI7Ij4KICAgIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuOTgzODI3LDAsMCwwLjk4MzgyNywxMy42NjEsLTAuMjg0Njk5KSI+CiAgICAgICAgPHBhdGggZD0iTTcyMy4yNTMsNDkzLjcwNEM3NTYuMjg4LDQ5NS4yMiA3ODIuNjQ0LDUyMi41MiA3ODIuNjQ0LDU1NS45MjdMNzgyLjY0NCw4MzQuMzA0Qzc4Mi42NDQsODY4LjY4MyA3NTQuNzMzLDg5Ni41OTMgNzIwLjM1NSw4OTYuNTkzTDM1MS4zOTMsODk2LjU5M0MzMTcuMDE1LDg5Ni41OTMgMjg5LjEwNCw4NjguNjgzIDI4OS4xMDQsODM0LjMwNEwyODkuMTA0LDU1NS45MjdDMjg5LjEwNCw1MjMuMTE3IDMxNC41MjYsNDk2LjE5OCAzNDYuNzMsNDkzLjgxTDM0Ni43MywzOTAuMTE5QzM0Ni43MywyODYuMjE1IDQzMS4wODgsMjAxLjg1OCA1MzQuOTkyLDIwMS44NThDNjM4Ljg5NiwyMDEuODU4IDcyMy4yNTMsMjg2LjIxNSA3MjMuMjUzLDM5MC4xMTlMNzIzLjI1Myw0OTMuNzA0Wk00MzMuMzI4LDQ5MC45NjZMNjM2LjY4Niw0OTIuMTE2QzYzNi42ODYsNDkyLjExNiA2MzcuMTQyLDM4NS4xMzIgNjM3LjA4NiwzODQuNDg5QzYzMS44NDksMzI0LjE2MyA1NzkuMTE4LDI4OC4wNzkgNTM0Ljk5MiwyODguNTM1QzQ5My4wMTcsMjg4Ljk2OSA0MzUuOTIsMzE4LjEgNDMzLjY1NiwzODMuNzk3QzQzMy40NzYsMzg5LjAxNyA0MzMuMzI4LDQ5MC45NjYgNDMzLjMyOCw0OTAuOTY2Wk01MDMuMjk2LDcxNC4zODJMNDkyLjQzMyw3ODQuNDU1QzQ5Mi40MzMsNzg0LjQ1NSA0OTAuMzc2LDc5OC4yODQgNDkyLjQxNiw4MDIuNjY0QzQ5Ni43OCw4MTIuMDMxIDUwMy44MjIsODExLjExMSA1MDMuODIyLDgxMS4xMTFMNTY1LjYsODEwLjgzOEM1NjUuNiw4MTAuODM4IDU3Mi44NjMsODExLjQ3MiA1NzcuNjM3LDgwMi4xNTVDNTc5Ljg4LDc5Ny43NzUgNTc3LjMyMyw3ODMuNzQgNTc3LjMyMyw3ODMuNzRMNTY2LjY0OSw3MTQuNDAxQzU5MC43NDMsNzAyLjY0OSA2MDcuMzU5LDY3Ny45MDggNjA3LjM1OSw2NDkuMzE4QzYwNy4zNTksNjA5LjM3NyA1NzQuOTMyLDU3Ni45NSA1MzQuOTkyLDU3Ni45NUM0OTUuMDUxLDU3Ni45NSA0NjIuNjI0LDYwOS4zNzcgNDYyLjYyNCw2NDkuMzE4QzQ2Mi42MjQsNjc3Ljg5MyA0NzkuMjIzLDcwMi42MjIgNTAzLjI5Niw3MTQuMzgyWiIgc3R5bGU9ImZpbGw6cmdiKDAsMTUyLDI0OCk7Ii8+CiAgICA8L2c+Cjwvc3ZnPgo="
},
"b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{
"name": "Zoho Vault",
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=",
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4="
},
"b35a26b2-8f6e-4697-ab1d-d44db4da28c6":{
"name": "Zoho Vault",
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=",
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICMyMjZlYjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0zIHsKICAgICAgICBvcGFjaXR5OiAuOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZTQyNTI4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yNi4xLDMuNjJINS45Yy0xLjI0LDAtMi4yNSwxLjAxLTIuMjUsMi4yNXYxNi45NGMwLDEuMjQsMS4wMSwyLjI1LDIuMjUsMi4yNWguMWMuMy4wNy42Ny4yOS42Ny45MXYyLjExYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTEuMjNzLjA3LTEuNTYsMS43NS0xLjc5aDguNThjMS42OC4yMywxLjc1LDEuNzksMS43NSwxLjc5djEuMjNjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMi4xMWMwLS42Mi4zNy0uODMuNjctLjkxaC4wOWMxLjI0LDAsMi4yNS0xLjAxLDIuMjUtMi4yNVY1Ljg3YzAtMS4yNC0xLjAxLTIuMjUtMi4yNS0yLjI1WiIvPgogIDxnPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjUuMDYsMzBoLTIuNzVjLTEuMDYsMC0xLjkyLS44Ni0xLjkyLTEuOTJ2LTEuMTNjMC0uMTUtLjEyLS4yNy0uMjctLjI3aC04LjI0Yy0uMTUsMC0uMjcuMTItLjI3LjI3djEuMTNjMCwxLjA2LS44NiwxLjkyLTEuOTIsMS45MmgtMi43NWMtMS4wNiwwLTEuOTItLjg2LTEuOTItMS45MnYtMS40OWMtMS43Mi0uMzgtMy4wMi0xLjkyLTMuMDItMy43NlY1Ljg0YzAtMi4xMiwxLjcyLTMuODQsMy44NC0zLjg0aDIwLjMxYzIuMTIsMCwzLjg0LDEuNzIsMy44NCwzLjg0djE2Ljk5YzAsMS44NC0xLjMsMy4zOC0zLjAyLDMuNzZ2MS40OWMwLDEuMDYtLjg2LDEuOTItMS45MiwxLjkyWk0xMS44OCwyNS4wM2g4LjI0YzEuMDYsMCwxLjkyLjg2LDEuOTIsMS45MnYxLjEzYzAsLjE1LjEyLjI3LjI3LjI3aDIuNzVjLjE1LDAsLjI3LS4xMi4yNy0uMjd2LTIuMjJjMC0uNDYuMzctLjgyLjgyLS44MiwxLjIxLDAsMi4yLS45OSwyLjItMi4yVjUuODRjMC0xLjIxLS45OS0yLjItMi4yLTIuMkg1Ljg0Yy0xLjIxLDAtMi4yLjk5LTIuMiwyLjJ2MTYuOTljMCwxLjIxLjk5LDIuMiwyLjIsMi4yLjQ2LDAsLjgyLjM3LjgyLjgydjIuMjJjMCwuMTUuMTIuMjcuMjcuMjdoMi43NWMuMTUsMCwuMjctLjEyLjI3LS4yN3YtMS4xM2MwLTEuMDYuODYtMS45MiwxLjkyLTEuOTJaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMC43NywxOS4yNWMtLjE3LDAtLjM0LS4wNS0uNDgtLjE2LS4zNy0uMjctLjQ1LS43OC0uMTgtMS4xNWwyLjY3LTMuNjhjLjI3LS4zNy43OC0uNDUsMS4xNS0uMTguMzcuMjcuNDUuNzguMTgsMS4xNWwtMi42NywzLjY4Yy0uMTYuMjItLjQxLjM0LS42Ny4zNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTE2LjEyLDE5LjI1Yy0uMjYsMC0uNTEtLjEyLS42Ny0uMzRsLTIuNjctMy42OGMtLjI3LS4zNy0uMTktLjg4LjE4LTEuMTUuMzctLjI3Ljg4LS4xOSwxLjE1LjE4bDIuNjcsMy42OGMuMjcuMzcuMTkuODgtLjE4LDEuMTUtLjE1LjExLS4zMi4xNi0uNDguMTZaIi8+CiAgICA8L2c+CiAgICA8ZyBjbGFzcz0iY2xzLTMiPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMy40NCwxNS41N2MtLjQ2LDAtLjgyLS4zNy0uODItLjgydi00LjUxYzAtLjQ2LjM3LS44Mi44Mi0uODJzLjgyLjM3LjgyLjgydjQuNTFjMCwuNDYtLjM3LjgyLS44Mi44MloiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMzUsMC0uNjctLjIyLS43OC0uNTctLjE0LS40My4xLS45LjUzLTEuMDRsNC4zMi0xLjM5Yy40My0uMTQuOS4xLDEuMDQuNTMuMTQuNDMtLjEuOS0uNTMsMS4wNGwtNC4zMiwxLjM5Yy0uMDkuMDMtLjE3LjA0LS4yNi4wNFoiLz4KICAgIDwvZz4KICAgIDxnIGNsYXNzPSJjbHMtMyI+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTEzLjQ0LDE1LjU4Yy0uMDksMC0uMTctLjAxLS4yNS0uMDRsLTQuMzItMS4zOWMtLjQzLS4xNC0uNjctLjYtLjUzLTEuMDQuMTQtLjQzLjYtLjY3LDEuMDQtLjUzbDQuMzIsMS4zOWMuNDMuMTQuNjcuNi41MywxLjA0LS4xMS4zNS0uNDMuNTctLjc4LjU3WiIvPgogICAgPC9nPgogICAgPGcgY2xhc3M9ImNscy0zIj4KICAgICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMjIuODUsMTkuMjNjLS40NiwwLS44Mi0uMzctLjgyLS44MnYtOC4xNWMwLS40Ni4zNy0uODIuODItLjgycy44Mi4zNy44Mi44MnY4LjE1YzAsLjQ2LS4zNy44Mi0uODIuODJaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4="
},
"b78a0a55-6ef8-d246-a042-ba0f6d55050c": {
"name": "LastPass",
"icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81NzQzKSIvPgo8cGF0aCBkPSJNMzkuMzUxOCAxNy42MzgzQzM4LjkwNSAxNy42MzgzIDM4LjU2NjggMTcuOTc0NyAzOC41NjY4IDE4LjQyODdWMzEuNTYwNEMzOC41NjY4IDMyLjAxMDggMzguOTAxNCAzMi4zNTA5IDM5LjM1MTggMzIuMzUwOUMzOS44MDIyIDMyLjM1MDkgNDAuMTM2OCAzMi4wMTQ0IDQwLjEzNjggMzEuNTYwNFYxOC40Mjg3QzQwLjEzNjggMTcuOTc4NCAzOS44MDIyIDE3LjYzODMgMzkuMzUxOCAxNy42MzgzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIxLjg5MTggMjIuNTQ0OUMyMC4zODE0IDIyLjU0NDkgMTkuMDk1NCAyMy43ODU3IDE5LjA5NTQgMjUuMzA2OUMxOS4wOTU0IDI2LjgyODEgMjAuMzI3MiAyOC4xMjMyIDIxLjgzNzUgMjguMTIzMkMyMy4zNDc4IDI4LjEyMzIgMjQuNjMzOSAyNi44ODI0IDI0LjYzMzkgMjUuMzYxMkMyNC42MzM5IDIzLjg0IDIzLjQwMjEgMjIuNTQ0OSAyMS44OTE4IDIyLjU0NDlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzEuMDY5NCAyMi41NDQ5QzI5LjU1OTEgMjIuNTQ0OSAyOC4yNzMxIDIzLjc4NTcgMjguMjczMSAyNS4zMDY5QzI4LjI3MzEgMjYuODI4MSAyOS41MDQ4IDI4LjEyMzIgMzEuMDE1MiAyOC4xMjMyQzMyLjUyNTUgMjguMTIzMiAzMy44MTE1IDI2Ljg4MjQgMzMuODExNSAyNS4zNjEyQzMzLjg2NTggMjMuODQgMzIuNjM0IDIyLjU0NDkgMzEuMDY5NCAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjY1OTYgMjIuNTQ0OUMxMS4xNDkzIDIyLjU0NDkgOS44NjMyOCAyMy43ODU3IDkuODYzMjggMjUuMzA2OUM5Ljg2MzI4IDI2LjgyODEgMTEuMDk1MSAyOC4xMjMyIDEyLjYwNTQgMjguMTIzMkMxNC4xMTU3IDI4LjEyMzIgMTUuNDAxNyAyNi44ODI0IDE1LjQwMTcgMjUuMzYxMkMxNS40MDE3IDIzLjg0IDE0LjE3IDIyLjU0NDkgMTIuNjU5NiAyMi41NDQ5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODRfNTc0MyIgeDE9IjI1IiB5MT0iMCIgeDI9IjI1IiB5Mj0iNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzNDM0QzRCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyNjI2MjYiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K",
"icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iNy44NDc4MiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzI4NF81Nzc0KSIvPgo8cGF0aCBkPSJNMzkuMzUxNyAxNy42Mzg0QzM4LjkwNDkgMTcuNjM4NCAzOC41NjY3IDE3Ljk3NDkgMzguNTY2NyAxOC40Mjg5VjMxLjU2MDVDMzguNTY2NyAzMi4wMTA4IDM4LjkwMTMgMzIuMzUwOSAzOS4zNTE3IDMyLjM1MDlDMzkuODAyMSAzMi4zNTA5IDQwLjEzNjcgMzIuMDE0NSA0MC4xMzY3IDMxLjU2MDVWMTguNDI4OUM0MC4xMzY3IDE3Ljk3ODUgMzkuODAyMSAxNy42Mzg0IDM5LjM1MTcgMTcuNjM4NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS44OTE4IDIyLjU0NUMyMC4zODE0IDIyLjU0NSAxOS4wOTU0IDIzLjc4NTkgMTkuMDk1NCAyNS4zMDdDMTkuMDk1NCAyNi44MjgyIDIwLjMyNzIgMjguMTIzMyAyMS44Mzc1IDI4LjEyMzNDMjMuMzQ3OCAyOC4xMjMzIDI0LjYzMzggMjYuODgyNSAyNC42MzM4IDI1LjM2MTNDMjQuNjMzOCAyMy44NDAxIDIzLjQwMjEgMjIuNTQ1IDIxLjg5MTggMjIuNTQ1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxLjA2OTQgMjIuNTQ1QzI5LjU1OTEgMjIuNTQ1IDI4LjI3MyAyMy43ODU5IDI4LjI3MyAyNS4zMDdDMjguMjczIDI2LjgyODIgMjkuNTA0OCAyOC4xMjMzIDMxLjAxNTEgMjguMTIzM0MzMi41MjU0IDI4LjEyMzMgMzMuODExNSAyNi44ODI1IDMzLjgxMTUgMjUuMzYxM0MzMy44NjU3IDIzLjg0MDEgMzIuNjM0IDIyLjU0NSAzMS4wNjk0IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMi42NTk3IDIyLjU0NUMxMS4xNDk0IDIyLjU0NSA5Ljg2MzM5IDIzLjc4NTkgOS44NjMzOSAyNS4zMDdDOS44NjMzOSAyNi44MjgyIDExLjA5NTIgMjguMTIzMyAxMi42MDU1IDI4LjEyMzNDMTQuMTE1OCAyOC4xMjMzIDE1LjQwMTggMjYuODgyNSAxNS40MDE4IDI1LjM2MTNDMTUuNDAxOCAyMy44NDAxIDE0LjE3IDIyLjU0NSAxMi42NTk3IDIyLjU0NVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMjg0XzU3NzQiIHgxPSIyNSIgeTE9IjAiIHgyPSIyNSIgeTI9IjUwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNFRDE5NEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjQzMwODMzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="
},
"de503f9c-21a4-4f76-b4b7-558eb55c6f89": {
"name": "Devolutions",
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
}
"de503f9c-21a4-4f76-b4b7-558eb55c6f89": {
"name": "Devolutions",
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
}
}

File diff suppressed because one or more lines are too long

View File

@ -26,6 +26,7 @@ from authentik.flows.models import FlowDesignation
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET
from authentik.lib.avatars import DEFAULT_AVATAR
from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.utils.urls import reverse_with_qs
from authentik.root.middleware import ClientIPMiddleware
@ -76,7 +77,7 @@ class IdentificationChallenge(Challenge):
allow_show_password = BooleanField(default=False)
application_pre = CharField(required=False)
flow_designation = ChoiceField(FlowDesignation.choices)
captcha_stage = CaptchaChallenge(required=False)
captcha_stage = CaptchaChallenge(required=False, allow_null=True)
enroll_url = CharField(required=False)
recovery_url = CharField(required=False)
@ -224,6 +225,8 @@ class IdentificationStageView(ChallengeStageView):
"js_url": current_stage.captcha_stage.js_url,
"site_key": current_stage.captcha_stage.public_key,
"interactive": current_stage.captcha_stage.interactive,
"pending_user": "",
"pending_user_avatar": DEFAULT_AVATAR,
}
if current_stage.captcha_stage
else None

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2024.10.2 Blueprint schema",
"title": "authentik 2024.10.5 Blueprint schema",
"required": [
"version",
"entries"
@ -5617,13 +5617,20 @@
"title": "Issuer mode",
"description": "Configure how the issuer field of the ID Token should be filled."
},
"jwks_sources": {
"jwt_federation_sources": {
"type": "array",
"items": {
"type": "integer",
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
}
},
"required": []
@ -5746,7 +5753,7 @@
"type": "string",
"title": "Cookie domain"
},
"jwks_sources": {
"jwt_federation_sources": {
"type": "array",
"items": {
"type": "integer",
@ -5754,6 +5761,13 @@
},
"title": "Any JWT signed by the JWK of the selected source can be used to authenticate."
},
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
},
"access_token_validity": {
"type": "string",
"minLength": 1,

View File

@ -47,7 +47,7 @@ func checkServer() int {
h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport),
}
url := fmt.Sprintf("http://%s/-/health/live/", config.Get().Listen.HTTP)
url := fmt.Sprintf("http://%s%s-/health/live/", config.Get().Listen.HTTP, config.Get().Web.Path)
res, err := h.Head(url)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")

View File

@ -61,7 +61,7 @@ var rootCmd = &cobra.Command{
ex := common.Init()
defer common.Defer()
u, err := url.Parse(fmt.Sprintf("http://%s", config.Get().Listen.HTTP))
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path))
if err != nil {
panic(err)
}

View File

@ -31,7 +31,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.10.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.10.5}
restart: unless-stopped
command: server
environment:
@ -49,10 +49,12 @@ services:
- "${COMPOSE_PORT_HTTP:-9000}:9000"
- "${COMPOSE_PORT_HTTPS:-9443}:9443"
depends_on:
- postgresql
- redis
postgresql:
condition: service_healthy
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.10.2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.10.5}
restart: unless-stopped
command: worker
environment:
@ -76,8 +78,10 @@ services:
env_file:
- .env
depends_on:
- postgresql
- redis
postgresql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
database:

8
go.mod
View File

@ -7,7 +7,7 @@ toolchain go1.23.0
require (
beryju.io/ldap v0.1.0
github.com/coreos/go-oidc/v3 v3.11.0
github.com/getsentry/sentry-go v0.29.1
github.com/getsentry/sentry-go v0.30.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-openapi/runtime v0.28.0
@ -27,12 +27,12 @@ require (
github.com/sethvargo/go-envconfig v1.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024102.2
goauthentik.io/api/v3 v3.2024105.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.9.0
golang.org/x/sync v0.10.0
gopkg.in/yaml.v2 v2.4.0
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
)

16
go.sum
View File

@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA=
github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0=
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -274,8 +274,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wwt/guac v1.3.2 h1:sH6OFGa/1tBs7ieWBVlZe7t6F5JAOWBry/tqQL/Vup4=
github.com/wwt/guac v1.3.2/go.mod h1:eKm+NrnK7A88l4UBEcYNpZQGMpZRryYKoz4D/0/n1C0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2024102.2 h1:k2sIU7TkT2fOomBYo5KEc/mz5ipzaZUp5TuEOJLPX4g=
goauthentik.io/api/v3 v3.2024102.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2024105.1 h1:PxOlStLdM+L80ciVJUWZRhf2VQrDVnNMcv+exeQ/qUA=
goauthentik.io/api/v3 v3.2024105.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -400,8 +400,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -14,6 +14,7 @@ type Config struct {
// Config for both core and outposts
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"`
Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"`
Web WebConfig `yaml:"web" env:", prefix=AUTHENTIK_WEB__"`
// Outpost specific config
// These are only relevant for proxy/ldap outposts, and cannot be set via YAML
@ -72,3 +73,7 @@ type OutpostConfig struct {
Discover bool `yaml:"discover" env:"DISCOVER, overwrite"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"`
}
type WebConfig struct {
Path string `yaml:"path" env:"PATH, overwrite"`
}

View File

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

View File

@ -56,10 +56,10 @@ type APIController struct {
func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
config := api.NewConfiguration()
config.Host = akURL.Host
config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{
apiConfig := api.NewConfiguration()
apiConfig.Host = akURL.Host
apiConfig.Scheme = akURL.Scheme
apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport(
constants.OutpostUserAgent(),
web.NewTracingTransport(
@ -68,10 +68,15 @@ func NewAPIController(akURL url.URL, token string) *APIController {
),
),
}
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
apiConfig.Servers = api.ServerConfigurations{
{
URL: fmt.Sprintf("%sapi/v3", akURL.Path),
},
}
apiConfig.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
// create the API client, with the transport
apiClient := api.NewAPIClient(config)
apiClient := api.NewAPIClient(apiConfig)
log := log.WithField("logger", "authentik.outpost.ak-api-controller")

View File

@ -17,7 +17,7 @@ import (
)
func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
pathTemplate := "%s://%s/ws/outpost/%s/?%s"
pathTemplate := "%s://%s%sws/outpost/%s/?%s"
query := akURL.Query()
query.Set("instance_uuid", ac.instanceUUID.String())
scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws")
@ -37,7 +37,7 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
},
}
ws, _, err := dialer.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, outpostUUID, akURL.Query().Encode()), header)
ws, _, err := dialer.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, akURL.Path, outpostUUID, akURL.Query().Encode()), header)
if err != nil {
ac.logger.WithError(err).Warning("failed to connect websocket")
return err
@ -83,6 +83,7 @@ func (ac *APIController) reconnectWS() {
u := url.URL{
Host: ac.Client.GetConfig().Host,
Scheme: ac.Client.GetConfig().Scheme,
Path: strings.ReplaceAll(ac.Client.GetConfig().Servers[0].URL, "api/v3", ""),
}
attempt := 1
for {

View File

@ -46,7 +46,7 @@ func (ws *WebServer) runMetricsServer() {
).ServeHTTP(rw, r)
// Get upstream metrics
re, err := http.NewRequest("GET", fmt.Sprintf("%s/-/metrics/", ws.ul.String()), nil)
re, err := http.NewRequest("GET", fmt.Sprintf("%s%s-/metrics/", ws.upstreamURL.String(), config.Get().Web.Path), nil)
if err != nil {
l.WithError(err).Warning("failed to get upstream metrics")
return

View File

@ -9,14 +9,15 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
)
func (ws *WebServer) configureProxy() {
// Reverse proxy to the application server
director := func(req *http.Request) {
req.URL.Scheme = ws.ul.Scheme
req.URL.Host = ws.ul.Host
req.URL.Scheme = ws.upstreamURL.Scheme
req.URL.Host = ws.upstreamURL.Host
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
@ -32,7 +33,10 @@ func (ws *WebServer) configureProxy() {
}
rp.ErrorHandler = ws.proxyErrorHandler
rp.ModifyResponse = ws.proxyModifyResponse
ws.m.PathPrefix("/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
ws.mainRouter.PathPrefix(config.Get().Web.Path).Path("/-/health/live/").HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(200)
}))
ws.mainRouter.PathPrefix(config.Get().Web.Path).HandlerFunc(sentry.SentryNoSample(func(rw http.ResponseWriter, r *http.Request) {
if !ws.g.IsRunning() {
ws.proxyErrorHandler(rw, r, errors.New("authentik starting"))
return

View File

@ -14,46 +14,74 @@ import (
)
func (ws *WebServer) configureStatic() {
staticRouter := ws.lh.NewRoute().Subrouter()
// Setup routers
staticRouter := ws.loggingRouter.NewRoute().Subrouter()
staticRouter.Use(ws.staticHeaderMiddleware)
staticRouter.Use(web.DisableIndex)
indexLessRouter := staticRouter.NewRoute().Subrouter()
// Specifically disable index
indexLessRouter.Use(web.DisableIndex)
distFs := http.FileServer(http.Dir("./web/dist"))
authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik")))
// Root file paths, from which they should be accessed
staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs))
staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
pathStripper := func(handler http.Handler, paths ...string) http.Handler {
h := handler
for _, path := range paths {
h = http.StripPrefix(path, h)
}
return h
}
// Also serve assets folder in specific interfaces since fonts in patternfly are imported
// with a relative path
staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
helpHandler := http.FileServer(http.Dir("./website/help/"))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/dist/").Handler(pathStripper(
distFs,
"static/dist/",
config.Get().Web.Path,
))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/authentik/").Handler(pathStripper(
http.FileServer(http.Dir("./web/authentik")),
"static/authentik/",
config.Get().Web.Path,
))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
pathStripper(
distFs,
"if/flow/"+vars["flow_slug"],
config.Get().Web.Path,
).ServeHTTP(rw, r)
})
staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/admin/assets").Handler(http.StripPrefix(fmt.Sprintf("%sif/admin", config.Get().Web.Path), distFs))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/user/assets").Handler(http.StripPrefix(fmt.Sprintf("%sif/user", config.Get().Web.Path), distFs))
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r)
pathStripper(
distFs,
"if/rac/"+vars["app_slug"],
config.Get().Web.Path,
).ServeHTTP(rw, r)
})
// Media files, if backend is file
if config.Get().Storage.Media.Backend == "file" {
fsMedia := http.StripPrefix("/media", http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)))
staticRouter.PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
fsMedia.ServeHTTP(w, r)
indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/media/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
fsMedia.ServeHTTP(w, r)
})
}
staticRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/"))))
staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper(
helpHandler,
config.Get().Web.Path,
"/if/help/",
))
staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/help").Handler(http.RedirectHandler(fmt.Sprintf("%sif/help/", config.Get().Web.Path), http.StatusMovedPermanently))
// Static misc files
ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
staticRouter.PathPrefix(config.Get().Web.Path).Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200)
_, err := rw.Write(staticWeb.RobotsTxt)
@ -61,7 +89,7 @@ func (ws *WebServer) configureStatic() {
ws.log.WithError(err).Warning("failed to write response")
}
})
ws.lh.Path("/.well-known/security.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
staticRouter.PathPrefix(config.Get().Web.Path).Path("/.well-known/security.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200)
_, err := rw.Write(staticWeb.SecurityTxt)

View File

@ -33,13 +33,13 @@ type WebServer struct {
ProxyServer *proxyv2.ProxyServer
BrandTLS *brand_tls.Watcher
g *gounicorn.GoUnicorn
gr bool
m *mux.Router
lh *mux.Router
log *log.Entry
uc *http.Client
ul *url.URL
g *gounicorn.GoUnicorn
gunicornReady bool
mainRouter *mux.Router
loggingRouter *mux.Router
log *log.Entry
upstreamClient *http.Client
upstreamURL *url.URL
}
const UnixSocketName = "authentik-core.sock"
@ -73,17 +73,22 @@ func NewWebServer() *WebServer {
u, _ := url.Parse("http://localhost:8000")
ws := &WebServer{
m: mainHandler,
lh: loggingHandler,
log: l,
gr: true,
uc: upstreamClient,
ul: u,
mainRouter: mainHandler,
loggingRouter: loggingHandler,
log: l,
gunicornReady: true,
upstreamClient: upstreamClient,
upstreamURL: u,
}
ws.configureStatic()
ws.configureProxy()
// Redirect for sub-folder
if sp := config.Get().Web.Path; sp != "/" {
ws.mainRouter.Path("/").Handler(http.RedirectHandler(sp, http.StatusFound))
}
hcUrl := fmt.Sprintf("%s%s-/health/live/", ws.upstreamURL.String(), config.Get().Web.Path)
ws.g = gounicorn.New(func() bool {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/-/health/live/", ws.ul.String()), nil)
req, err := http.NewRequest(http.MethodGet, hcUrl, nil)
if err != nil {
ws.log.WithError(err).Warning("failed to create request for healthcheck")
return false
@ -107,7 +112,7 @@ func (ws *WebServer) Start() {
func (ws *WebServer) attemptStartBackend() {
for {
if !ws.gr {
if !ws.gunicornReady {
return
}
err := ws.g.Start()
@ -135,7 +140,7 @@ func (ws *WebServer) Core() *gounicorn.GoUnicorn {
}
func (ws *WebServer) upstreamHttpClient() *http.Client {
return ws.uc
return ws.upstreamClient
}
func (ws *WebServer) Shutdown() {
@ -160,7 +165,7 @@ func (ws *WebServer) listenPlain() {
func (ws *WebServer) serve(listener net.Listener) {
srv := &http.Server{
Handler: ws.m,
Handler: ws.mainRouter,
}
// See https://golang.org/pkg/net/http/#Server.Shutdown

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-18 00:09+0000\n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -73,8 +73,8 @@ msgid "authentik Export - {date}"
msgstr ""
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-format
msgid "Successfully imported %(count)d files."
#, python-brace-format
msgid "Successfully imported {count} files."
msgstr ""
#: authentik/brands/models.py
@ -856,13 +856,13 @@ msgid "Starting full provider sync"
msgstr ""
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of users"
#, python-brace-format
msgid "Syncing page {page} of users"
msgstr ""
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of groups"
#, python-brace-format
msgid "Syncing page {page} of groups"
msgstr ""
#: authentik/lib/sync/outgoing/tasks.py
@ -1012,8 +1012,8 @@ msgid "Event Matcher Policies"
msgstr ""
#: authentik/policies/expiry/models.py
#, python-format
msgid "Password expired %(days)d days ago. Please update your password."
#, python-brace-format
msgid "Password expired {days} days ago. Please update your password."
msgstr ""
#: authentik/policies/expiry/models.py
@ -1140,8 +1140,8 @@ msgid "Invalid password."
msgstr ""
#: authentik/policies/password/models.py
#, python-format
msgid "Password exists on %(count)d online lists."
#, python-brace-format
msgid "Password exists on {count} online lists."
msgstr ""
#: authentik/policies/password/models.py
@ -1252,6 +1252,11 @@ msgstr ""
msgid "Search full LDAP directory"
msgstr ""
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr ""
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr ""
@ -1294,6 +1299,14 @@ msgstr ""
msgid "Each provider has a different issuer, based on the application slug."
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)"
msgstr ""
@ -1370,10 +1383,6 @@ msgstr ""
msgid "Redirect URIs"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr ""

Binary file not shown.

View File

@ -11,7 +11,7 @@
# Mordecai, 2023
# Charles Leclerc, 2024
# nerdinator <florian.dupret@gmail.com>, 2024
# Titouan Petit, 2024
# Tina, 2024
# Marc Schmitt, 2024
#
#, fuzzy
@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-23 16:39+0000\n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -89,9 +89,9 @@ msgid "authentik Export - {date}"
msgstr "Export authentik - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-format
msgid "Successfully imported %(count)d files."
msgstr " %(count)d fichiers importés avec succès."
#, python-brace-format
msgid "Successfully imported {count} files."
msgstr "{count} fichiers importés avec succès."
#: authentik/brands/models.py
msgid ""
@ -121,6 +121,10 @@ msgstr "Marque"
msgid "Brands"
msgstr "Marques"
#: authentik/core/api/devices.py
msgid "Extra description not available"
msgstr "Description supplémentaire indisponible"
#: authentik/core/api/providers.py
msgid ""
"When not set all providers are returned. When set to true, only backchannel "
@ -131,6 +135,11 @@ msgstr ""
"fournisseurs backchannels sont retournés. Si faux, les fournisseurs "
"backchannels sont exclus"
#: authentik/core/api/transactional_applications.py
#, python-brace-format
msgid "User lacks permission to create {model}"
msgstr "L'utilisateur manque de permission pour créer {model}"
#: authentik/core/api/users.py
msgid "No leading or trailing slashes allowed."
msgstr ""
@ -933,14 +942,14 @@ msgid "Starting full provider sync"
msgstr "Démarrage d'une synchronisation complète du fournisseur"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of users"
msgstr "Synchronisation de la page %(page)d d'utilisateurs"
#, python-brace-format
msgid "Syncing page {page} of users"
msgstr "Synchronisation de la page {page} d'utilisateurs"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of groups"
msgstr "Synchronisation de la page %(page)d de groupes"
#, python-brace-format
msgid "Syncing page {page} of groups"
msgstr "Synchronisation de la page {page} de groupes"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
@ -1113,11 +1122,11 @@ msgid "Event Matcher Policies"
msgstr "Politiques d'association d'évènements"
#: authentik/policies/expiry/models.py
#, python-format
msgid "Password expired %(days)d days ago. Please update your password."
#, python-brace-format
msgid "Password expired {days} days ago. Please update your password."
msgstr ""
"Mot de passe expiré il y a %(days)d jours. Merci de mettre à jour votre mot "
"de passe."
"Mot de passe expiré il y a {days} jours. Merci de mettre à jour votre mot de"
" passe."
#: authentik/policies/expiry/models.py
msgid "Password has expired."
@ -1249,9 +1258,13 @@ msgid "Password not set in context"
msgstr "Mot de passe non défini dans le contexte"
#: authentik/policies/password/models.py
#, python-format
msgid "Password exists on %(count)d online lists."
msgstr "Le mot de passe existe sur %(count)d liste en ligne."
msgid "Invalid password."
msgstr "Mot de passe invalide."
#: authentik/policies/password/models.py
#, python-brace-format
msgid "Password exists on {count} online lists."
msgstr "Le mot de passe existe sur {count} listes en ligne."
#: authentik/policies/password/models.py
msgid "Password is too weak."
@ -1378,6 +1391,11 @@ msgstr "Fournisseurs LDAP"
msgid "Search full LDAP directory"
msgstr "Rechercher dans l'annuaire LDAP complet"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "Pattern de regex invalide : {url}"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "Basé sur le hash de l'ID utilisateur"
@ -1427,6 +1445,14 @@ msgstr ""
"Chaque fournisseur a un émetteur différent, basé sur le slug de "
"l'application."
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "Comparaison stricte d'URL"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "Correspondance d'URL par expression régulière"
#: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)"
msgstr "code (Authorization Code Flow)"
@ -1507,10 +1533,6 @@ msgstr "Secret du client"
msgid "Redirect URIs"
msgstr "URIs de redirection"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "Entrez chaque URI sur une nouvelle ligne."
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr "Include les demandes utilisateurs dans id_token"
@ -2889,13 +2911,8 @@ msgid "Captcha Stages"
msgstr "Étapes de Captcha"
#: authentik/stages/captcha/stage.py
msgid "Unknown error"
msgstr "Erreur inconnue"
#: authentik/stages/captcha/stage.py
#, python-brace-format
msgid "Failed to validate token: {error}"
msgstr "Échec de validation du jeton : {error}"
msgid "Invalid captcha response. Retrying may solve this issue."
msgstr "Réponse captcha invalide. Réessayer peut résoudre ce problème."
#: authentik/stages/captcha/stage.py
msgid "Invalid captcha response"
@ -3562,6 +3579,11 @@ msgstr ""
msgid "Globally enable/disable impersonation."
msgstr "Activer/désactiver l'appropriation utilisateur de manière globale."
#: authentik/tenants/models.py
msgid "Require administrators to provide a reason for impersonating a user."
msgstr ""
"Forcer les administrateurs à fournir une raison d'appropriation utilisateur."
#: authentik/tenants/models.py
msgid "Default token duration"
msgstr "Durée par défaut des jetons"

Binary file not shown.

View File

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-18 00:09+0000\n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -82,9 +82,9 @@ msgid "authentik Export - {date}"
msgstr "authentik 导出 - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-format
msgid "Successfully imported %(count)d files."
msgstr "已成功导入 %(count)d 个文件。"
#, python-brace-format
msgid "Successfully imported {count} files."
msgstr "已成功导入 {count} 个文件。"
#: authentik/brands/models.py
msgid ""
@ -868,14 +868,14 @@ msgid "Starting full provider sync"
msgstr "开始全量提供程序同步"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of users"
msgstr "正在同步用户页面 %(page)d"
#, python-brace-format
msgid "Syncing page {page} of users"
msgstr "正在同步用户页面 {page}"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of groups"
msgstr "正在同步群组页面 %(page)d"
#, python-brace-format
msgid "Syncing page {page} of groups"
msgstr "正在同步群组页面 {page}"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
@ -1026,9 +1026,9 @@ msgid "Event Matcher Policies"
msgstr "事件匹配策略"
#: authentik/policies/expiry/models.py
#, python-format
msgid "Password expired %(days)d days ago. Please update your password."
msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
#, python-brace-format
msgid "Password expired {days} days ago. Please update your password."
msgstr "密码在 {days} 天前过期。请更新您的密码。"
#: authentik/policies/expiry/models.py
msgid "Password has expired."
@ -1154,9 +1154,9 @@ msgid "Invalid password."
msgstr "无效密码。"
#: authentik/policies/password/models.py
#, python-format
msgid "Password exists on %(count)d online lists."
msgstr "%(count)d 个在线列表中存在密码。"
#, python-brace-format
msgid "Password exists on {count} online lists."
msgstr "{count} 个在线列表中存在密码。"
#: authentik/policies/password/models.py
msgid "Password is too weak."
@ -1275,6 +1275,11 @@ msgstr "LDAP 提供程序"
msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "无效的正则表达式模式:{url}"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID"
@ -1317,6 +1322,14 @@ msgstr "所有提供程序都使用相同的标识符"
msgid "Each provider has a different issuer, based on the application slug."
msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。"
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "严格 URL 比较"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "正则表达式 URL 匹配"
#: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)"
msgstr "code授权码流程"
@ -1393,10 +1406,6 @@ msgstr "客户端密钥"
msgid "Redirect URIs"
msgstr "重定向 URI"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "每行输入一个 URI。"
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr "在 id_token 中包含声明"

View File

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-18 00:09+0000\n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2024\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -81,9 +81,9 @@ msgid "authentik Export - {date}"
msgstr "authentik 导出 - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-format
msgid "Successfully imported %(count)d files."
msgstr "已成功导入 %(count)d 个文件。"
#, python-brace-format
msgid "Successfully imported {count} files."
msgstr "已成功导入 {count} 个文件。"
#: authentik/brands/models.py
msgid ""
@ -867,14 +867,14 @@ msgid "Starting full provider sync"
msgstr "开始全量提供程序同步"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of users"
msgstr "正在同步用户页面 %(page)d"
#, python-brace-format
msgid "Syncing page {page} of users"
msgstr "正在同步用户页面 {page}"
#: authentik/lib/sync/outgoing/tasks.py
#, python-format
msgid "Syncing page %(page)d of groups"
msgstr "正在同步群组页面 %(page)d"
#, python-brace-format
msgid "Syncing page {page} of groups"
msgstr "正在同步群组页面 {page}"
#: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format
@ -1025,9 +1025,9 @@ msgid "Event Matcher Policies"
msgstr "事件匹配策略"
#: authentik/policies/expiry/models.py
#, python-format
msgid "Password expired %(days)d days ago. Please update your password."
msgstr "密码在 %(days)d 天前过期。请更新您的密码。"
#, python-brace-format
msgid "Password expired {days} days ago. Please update your password."
msgstr "密码在 {days} 天前过期。请更新您的密码。"
#: authentik/policies/expiry/models.py
msgid "Password has expired."
@ -1153,9 +1153,9 @@ msgid "Invalid password."
msgstr "无效密码。"
#: authentik/policies/password/models.py
#, python-format
msgid "Password exists on %(count)d online lists."
msgstr "%(count)d 个在线列表中存在密码。"
#, python-brace-format
msgid "Password exists on {count} online lists."
msgstr "{count} 个在线列表中存在密码。"
#: authentik/policies/password/models.py
msgid "Password is too weak."
@ -1274,6 +1274,11 @@ msgstr "LDAP 提供程序"
msgid "Search full LDAP directory"
msgstr "搜索完整 LDAP 目录"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "无效的正则表达式模式:{url}"
#: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID"
msgstr "基于经过哈希处理的用户 ID"
@ -1316,6 +1321,14 @@ msgstr "所有提供程序都使用相同的标识符"
msgid "Each provider has a different issuer, based on the application slug."
msgstr "根据应用程序 Slug每个提供程序都有不同的颁发者。"
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "严格 URL 比较"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "正则表达式 URL 匹配"
#: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)"
msgstr "code授权码流程"
@ -1392,10 +1405,6 @@ msgstr "客户端密钥"
msgid "Redirect URIs"
msgstr "重定向 URI"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "每行输入一个 URI。"
#: authentik/providers/oauth2/models.py
msgid "Include claims in id_token"
msgstr "在 id_token 中包含声明"

View File

@ -1,5 +1,5 @@
{
"name": "@goauthentik/authentik",
"version": "2024.10.2",
"version": "2024.10.5",
"private": true
}

953
poetry.lock generated

File diff suppressed because it is too large Load Diff

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