Compare commits

..

178 Commits

Author SHA1 Message Date
906c63c16f blueprints: add FindObject tag
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-19 15:55:34 +01:00
7763a3673c core: bump msgraph-sdk from 1.14.0 to 1.15.0 (#12403)
Bumps [msgraph-sdk](https://github.com/microsoftgraph/msgraph-sdk-python) from 1.14.0 to 1.15.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.14.0...v1.15.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-19 12:02:42 +01:00
d99005e130 core: bump pydantic from 2.10.3 to 2.10.4 (#12404)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.3 to 2.10.4.
- [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.3...v2.10.4)

---
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-19 12:02:30 +01:00
c61f96e770 core: bump google-api-python-client from 2.155.0 to 2.156.0 (#12405)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.155.0 to 2.156.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.155.0...v2.156.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  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-19 12:02:19 +01:00
83622dd934 core: bump goauthentik.io/api/v3 from 3.2024105.3 to 3.2024105.5 (#12406)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024105.3 to 3.2024105.5.
- [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.2024105.3...v3.2024105.5)

---
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-19 12:02:09 +01:00
2eebd0eaa1 translate: Updates for file web/xliff/en.xlf in zh_CN (#12402)
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-12-19 09:30:54 +01:00
b61d918c5c translate: Updates for file web/xliff/en.xlf in zh-Hans (#12401)
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-12-19 09:30:48 +01:00
076a4f4772 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12400)
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-12-19 09:30:35 +01:00
b3872b35f8 translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12399)
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-12-19 09:29:53 +01:00
f06534cdf0 website/docs: release: 2024.12: add latest changes (#12397) 2024-12-18 18:35:07 +00:00
c528a6c336 web/admin: add application bindings to the application wizard (#11462)
* web: fix Flash of Unstructured Content while SearchSelect is loading from the backend

Provide an alternative, readonly, disabled, unindexed input object with the text "Loading...", to be
replaced with the _real_ input element after the content is loaded.

This provides the correct appearance and spacing so the content doesn't jiggle about between the
start of loading and the SearchSelect element being finalized.  It was visually distracting and
unappealing.

* web: comment on state management in API layer, move file to point to correct component under test.

* web: test for flash of unstructured content

- Add a unit test to ensure the "Loading..." element is displayed correctly before data arrives
- Demo how to mock a `fetchObjects()` call in testing. Very cool.
- Make distinguishing rule sets for code, tests, and scripts in nightmare mode
- In SearchSelect, Move the `styles()` declaration to the top of the class for consistency.

- To test for the FLOUC issue in SearchSelect.

This is both an exercise in mocking @beryju's `fetchObjects()` protocol, and shows how we can unit
test generic components that render API objects.

* web: interim commit of the basic sortable & selectable table.

* web: added basic unit testing to API-free tables

Mostly these tests assert that the table renders and that the content we give it
is where we expect it to be after sorting. For select tables, it also asserts that
the overall value of the table is what we expect it to be when we click on a
single row, or on the "select all" button.

* web: finalize testing for tables

Includes documentation updates and better tests for select-table.

* Provide unit test accessibility to Firefox and Safari; wrap calls to manipulate test DOMs directly in a browser.exec call so they run in the proper context and be await()ed properly

* web: repeat is needed to make sure sub-elements move around correctly. Map does not do full tracking.

* web: Update HorizontalLightComponent to accurately convey its value "upwards."

* interim commit, gods, the CSS is finally working.

* web: update

Got the binding editor in.  The tests complete.  Removed sonarjs.

* web: fixed tests to complete.

* web: fixed round-trip between binding list and binding editor. Fixed 'delete'.  TODO: Fix error reporting on home page, the edit button is ugly, and the height is off somehow, but I'm not yet sure how. I just know it bugs my eyes.

* core: add support to set policy bindings in transactional endpoint

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

* improve permission checks

especially since we'll be using the wizard as default in the future, it shouldn't be superuser only

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

* web: update api-less tables

- Replace `th` with `td` in `thead` components. Because Patternfly.
- Add @beryju's styling to the tables, which make it much better looking

* web: wizard for applications, now with bindings!

- Add policy bindings to the application wizard

- Restructures the Wizard base code.
  - ak-wizard-steps holds the steps and listens for NavigationRequest events to move
    from one step to the next.
  - WizardStep is a base class (no component registration provided) that provides the *whole frame*,
    not just the form.  It receives the navigation content for the sidebar from ak-wizard-steps,
    and provides the styling for the header, footer, sidebar, and main form.  It has abstractions
    for `buttons`, `renderMain()`, `handleButton()`, `handleEnable()`, in a section well-marked as
    "Public API".  Steps inherit from this class.

Conceptually:

- A wizard is a series of pages ("steps") with a distinct beginning and end, linked in a series,
  to complete a task.
- Later steps in the series are inaccessible until an earlier steps has granted access to it.
- Access is predicated on the earlier step being complete and valid. The developer is responsible
  for determining what "complete and valid" means.
- The series is visible, giving the customer a sense of how much effort is needed to complete the
  task.
- A parent object maintains (and can modify as needed) the list of steps. It *can* maintain the
  information being collected from the user. Alternatively, that information can be kept in each
  step.

Details:

- Keeping with the Lit paradigm, "requests to change the system flow up, information changed by
  valid requests flows down."
- The information flows up using events: WizardNavigation, WizardUpdate, WizardClose.
- The information flows down using properties.

- ak-application-wizard-main holds the list of steps, providing a unique slot name for each.
  - It maintains the ApplicationWizardState object.
- ApplicationWizardStep inherits from WizardStep and provides:
  - A means of extraction information from forms
  - A convenience method for updating the ApplicationWizardState object, enabling future steps, and
    navigating to a future step, in the correct order.
  - A method for cleaning error from the error reporting mechanism as the user navigates from an
    error-handling state.
  - The title, description, and cancelability of the wizard.
- Steps:
  - step: Handles the application. A good starting point for understanding the point of
    the Wizard.  Check the `handleButton()` method to understand how we enable or disable access to
    future steps.
  - provider-choice: Just a list. Shows validation without the form.
  - provider: Uses a *very* esoteric Lit feature, `unsafeStaticTag`, which enables
    the display to show anything that conforms to the expectations of ApplicationWizardProviderForm.
    - ApplicationWizardProviderForm repeats some of the base of ApplicationWizardStep, but allows us
      to provide multiple variants on a single form without having to create separate steps for each
      form.
    - The forms (`provider-for-ldap`, `provider-for-radius`) are therefore *just* the form and any
      fetchers needed to populate it.
  - bindings: Shows the table of bindings.  Has a custom display for "This table is empty."
  - edit-binding: Showcase for the `SearchSelectEZ` configuration format. Has an override on the
    `handleButton` feature to figure out which binding is about to be overridden. Is also a
    `.hidden` page; it doesn't show up on the navigation sidebar, as is only navigable-to by buttons
    not associated with the button bar at the bottom.
  - submit: Has a lot of machinery of state: Reviewing with errors, reviewing without errors,
    running submission, and success. Uses `ts-pattern` a lot to make sure the state/request pairs
    make sense.

The key insight is that, even though a wizard is a series in order, that order can't be simply
maintained in a list. The parent needs various strategies for swapping pages in and out of the
sequence, while still maintaining a coherent idea of "flow" and providing the visual cues the user
needs to feel confident that the work can be completed and completed quickly. The entire mechanism
for using an array and index to navigate, with index numbering, blocked the implementation of the
bindings pages.

One thing led to another.  *Sigh*  Really wish this hadn't been as much of a mess as it turned out.
The end result is pretty good, though.  Definitely re-usable.

One important feature to note is that the wizard is *not* tied to the ModalButton object; it's
simply embedded in a modal as-needed.  This allows us to use wizards in other places, such as just
being in a DIV, or just a page on its own.

* web: rollback dependabot "upgrade" that broke testing

Dependabot rolled us into WebdriverIO 9.  While that's probably the
right thing to do, right now it breaks out end-to-end tests badly.
Dependabot's mucking with infrastructure should not be taken lightly,
especially in cases when the infrastructure is for DX, not UX, and
doesn't create a bigger attack surface on the running product.

* web: small fixes for wdio and lint

- Roll back another dependabot breaking change, this time to WebdriverIO
- Remove the redundant scripts wrapping ESLint for Precommit mode. Access to those modes is
  available through the flags to the `./web/scripts/eslint.mjs` script.
- Remove SonarJS checks until SonarJS is ESLint 9 compatible.
- Minor nitpicking.

* web: not sure where all these getElement() additions come from; did I add them?  Anyway, they were breaking the tests, they're a Wdio9-ism.

* package-lock.json update

* web: small fixes for wdio and lint

**PLEASE** Stop trying to upgrade WebdriverIO following Dependabot's instructions. The changes
between wdio8 and wdio9 are extensive enough to require a lot more manual intervention. The unit
tests fail in wdio 9, with the testbed driver Wdio uses to compile content to push to the browser
([vite](https://vitejs.dev) complaining:

```
2024-09-27T15:30:03.672Z WARN @wdio/browser-runner:vite: warning: Unrecognized default export in file /Users/ken/projects/dev/web/node_modules/@patternfly/patternfly/components/Dropdown/dropdown.css
  Plugin: postcss-lit
  File: /Users/ken/projects/dev/web/node_modules/@patternfly/patternfly/components/Dropdown/dropdown.css
[0-6] 2024-09-27T15:30:04.083Z INFO webdriver: BIDI COMMAND script.callFunction {"functionDeclaration":"<Function[976 bytes]>","awaitPromise":true,"arguments":[],"target":{"context":"8E608E6D13E355DFFC28112C236B73AF"}}
[0-6]  Error:  Test failed due to following error(s):
  - ak-search-select.test.ts: The requested module '/src/common/styles/authentik.css' does not provide an export named 'default': SyntaxError: The requested module '/src/common/styles/authentik.css' does not provide an export named 'default'

```

So until we can figure out why the Vite installation isn't liking our CSS import scheme, we'll
have to soldier on with what we have.  At least with Wdio 8, we get:

```
Spec Files:      7 passed, 7 total (100% completed) in 00:00:19
```

* Forgot to run prettier.

* web: small fixes for elements and forms

- provides a new utility, `_isSlug_`, used to verify a user input
- extends the ak-horizontal-component wrapper to have a stronger identity and available value
- updates the types that use the wrapper to be typed more strongly
  - (Why) The above are used in the wizard to get and store values
- fixes a bug in SearchSelectEZ that broke the display if the user didn't supply a `groupBy` field.
- Adds `@wdio/types` to the package file so eslint is satisfied wdio builds correctly
- updates the end-to-end test to understand the revised button identities on the login page
  - Running the end-to-end tests verifies that changes to the components listed above did not break
    the semantics of those components.

* Prettier had opinions

* Fix the oauth2 provider test.

* web: fix oauth2 provider.  Fix resolutions in package-lock.json

* Provide an error field for the form errors on the OAuth2 form.  Unfortunately, this does not solve the general problem that we have a UX issue with which stage bindings to show where now that we've introduced the Invalidation Stage.

* web: Add InvalidationFlow to Radius Provider dialogues

## What

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

## Note

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

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

* admin/web: the default invalidation flows for LDAP and Radius are different from the others.

* Updating the SAML Wizard page to correspond to the provider page.  *This is an intermediate fix to get the tests passing. It will probably be mooted with the next revision.*

* Making progress...

* web/admin: provider formectomy complete

* fix minor issues

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

* custom ordering for provider types

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

* fix css

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

* fix missing PFBase causing wrong font

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

* fix missing card for type select

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

* fix padding on last page

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

* add card to bindings

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

* web/element/wizard: fix the CSS cascade so the modifications to the title display don't affect the wiard header.

* web/elements/wizard: fix logic on unavailable / available / current indicators in nav bar.

* Debugging code is not needed.

* web: small visual fixes

As requested by reviewers:

- Fixed the height to 75% of the viewport
- Put 1rem of whitespace between the hint label and the Wizard startup button.

* web: disable lint check for cAsEfUnNy AtTrIbUtE nAmEs.

* Apply suggestions from code review

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

* rework title

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

* format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-18 18:44:27 +01:00
821f06ffdf translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#12393)
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-12-18 15:52:56 +00:00
e83d040a48 translate: Updates for file web/xliff/en.xlf in fr (#12394)
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-18 15:32:56 +00:00
9affd90850 root: add locale to codeowners (#12392)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-18 15:56:46 +01:00
80d84cb03f website/integrations: update argocd terraform examples (#12370) 2024-12-18 14:21:31 +00:00
a9cc5fdafe core, web: update translations (#12390)
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-12-18 15:17:49 +01:00
b45109afce web: bump API Client version (#12391)
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-18 13:59:24 +00:00
c8711d9f8f website/docs: 2024.12 release notes (#12300)
Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-12-18 13:39:17 +00:00
40a7135c0c core: app entitlements (#12090)
* core: initial app entitlements

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

* base off of pbm

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

* add tests and oauth2

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

* add to proxy

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

* rewrite to use bindings

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

* make policy bindings form and list more customizable

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

* fix tests

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

* double fix

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

* refine permissions

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

* add missing rbac modal to app entitlements

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

* separate scope for app entitlements

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

* include entitlements mapping in proxy

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

* fix tests

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

* add API validation to prevent policies from being bound to entitlements

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

* make preview

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

* add initial docs

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

* fix

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

* remove duplicate docs

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-18 14:32:44 +01:00
675a4a6788 translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#12388)
Translate locale/en/LC_MESSAGES/django.po in it

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

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-18 13:26:45 +00:00
98b5b75f29 blueprints: add AtIndex tag (#12386) 2024-12-18 13:10:37 +00:00
22b0a1bd23 web: bump API Client version (#12387)
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-18 13:57:38 +01:00
1a1d499833 sources/oauth: allow creation of user connection objects with parameters (#12195)
* sources/oauth: allow creation of user connection objects with parameters

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

* fix web

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

* tix tests

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

* add for all

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

* align

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-18 13:28:22 +01:00
1573cfbaa1 website: bump docusaurus-theme-openapi-docs from 4.3.0 to 4.3.1 in /website (#12373)
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.3.0 to 4.3.1.
- [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.1/packages/docusaurus-theme-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-theme-openapi-docs
  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-18 13:19:02 +01:00
b88ce32111 website: bump aws-cdk from 2.173.1 to 2.173.2 in /website (#12374)
Bumps [aws-cdk](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk) from 2.173.1 to 2.173.2.
- [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.173.2/packages/aws-cdk)

---
updated-dependencies:
- dependency-name: aws-cdk
  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-18 13:17:31 +01:00
a1965ceada website: bump docusaurus-plugin-openapi-docs from 4.3.0 to 4.3.1 in /website (#12375)
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.3.0 to 4.3.1.
- [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.1/packages/docusaurus-plugin-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-plugin-openapi-docs
  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-18 13:17:23 +01:00
9c536a1b4b core: bump django-pglock from 1.7.0 to 1.7.1 (#12376)
Bumps [django-pglock](https://github.com/AmbitionEng/django-pglock) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/AmbitionEng/django-pglock/releases)
- [Changelog](https://github.com/AmbitionEng/django-pglock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/AmbitionEng/django-pglock/compare/1.7.0...1.7.1)

---
updated-dependencies:
- dependency-name: django-pglock
  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-18 13:13:37 +01:00
f3e0ff2833 core: bump google-api-python-client from 2.154.0 to 2.155.0 (#12377)
Bumps [google-api-python-client](https://github.com/googleapis/google-api-python-client) from 2.154.0 to 2.155.0.
- [Release notes](https://github.com/googleapis/google-api-python-client/releases)
- [Commits](https://github.com/googleapis/google-api-python-client/compare/v2.154.0...v2.155.0)

---
updated-dependencies:
- dependency-name: google-api-python-client
  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-18 13:13:24 +01:00
06dc47b582 core: bump aws-cdk-lib from 2.172.0 to 2.173.2 (#12378)
Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk) from 2.172.0 to 2.173.2.
- [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.172.0...v2.173.2)

---
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-18 13:13:18 +01:00
a4bf24a039 core: bump pdoc from 15.0.0 to 15.0.1 (#12379)
* core: bump pdoc from 15.0.0 to 15.0.1

Bumps [pdoc](https://github.com/mitmproxy/pdoc) from 15.0.0 to 15.0.1.
- [Changelog](https://github.com/mitmproxy/pdoc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mitmproxy/pdoc/compare/v15...v15.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 13:12:49 +01:00
1715c3e268 core: bump ruff from 0.8.2 to 0.8.3 (#12380)
* core: bump ruff from 0.8.2 to 0.8.3

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 13:11:48 +01:00
feb3be7cee core: bump uvicorn from 0.32.1 to 0.34.0 (#12381)
* core: bump uvicorn from 0.32.1 to 0.34.0

Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.32.1 to 0.34.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.32.1...0.34.0)

---
updated-dependencies:
- dependency-name: uvicorn
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 13:11:07 +01:00
db05232f12 core: bump twilio from 9.3.8 to 9.4.1 (#12382)
* core: bump twilio from 9.3.8 to 9.4.1

Bumps [twilio](https://github.com/twilio/twilio-python) from 9.3.8 to 9.4.1.
- [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.8...9.4.1)

---
updated-dependencies:
- dependency-name: twilio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 13:10:46 +01:00
ebfa7dbcfc web/admin: fix prompt stage wording (#12384)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-18 13:07:51 +01:00
8c4dab7399 sources/saml: fix redirect not kept through SAML Source (#12372)
* fix missing name in tests

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

* fix redirect lost with saml source

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-18 13:07:17 +01:00
28d8fcc115 root: fix missing entries in codeowners (#12369)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-18 00:40:20 +01:00
c436205e3d website/docs: miscellaneous fixes (#12351)
* website/docs: miscellaneous fixes

* restore trailing space in signature delimiter

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

* add link to Brands

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

* fix inaccuracies

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

---------

Signed-off-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-12-18 00:31:07 +01:00
3f788e7abe website/integrations: vault: add external group documentation (#11994)
* Add external group documentation for vault

Signed-off-by: Mattis Haase <accounts@mattis-haase.de>

* Spelling changes, linting

---------

Signed-off-by: Mattis Haase <accounts@mattis-haase.de>
Co-authored-by: mzhaase <mattis@mattis-haase.de>
2024-12-17 13:15:08 -06:00
b7a1a9c107 providers/oauth2: make session deletion cascade to tokens (#12343)
* providers/oauth2: make session deletion cascade to tokens

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

* add note for why we're shadowing the field

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-17 18:23:42 +01:00
559ec290d0 website/integrations: added hoarder integration (#12161)
* add Hoarder integration

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* hoarder sorted alphabetically

* more detailed way of using an application

* Update index.md

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* Update index.md

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* Update index.md

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* Update index.md

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* Update index.md

Co-authored-by: 4d62 <github-user@sdko.org>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

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

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>

* test

* ran make website

---------

Signed-off-by: Miguel Palau <mpalauzarza@gmail.com>
Co-authored-by: 4d62 <github-user@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tana@goauthentik.com>
2024-12-17 10:46:35 -06:00
05279514f8 ci: setup action: remove unused dependencies on poetry install (#12365)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-12-17 15:22:13 +01:00
061275d243 translate: Updates for file web/xliff/en.xlf in zh_CN (#12337)
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-12-17 12:58:37 +01:00
30e7f7acbd translate: Updates for file web/xliff/en.xlf in zh-Hans (#12338)
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-12-17 12:58:33 +01:00
80ab39675c core: bump goauthentik.io/api/v3 from 3.2024105.1 to 3.2024105.3 (#12348)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2024105.1 to 3.2024105.3.
- [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.2024105.1...v3.2024105.3)

---
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-17 12:58:19 +01:00
e63f13c9fe stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#12352)
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-17 12:58:07 +01:00
08b07aebb9 translate: Updates for file locale/en/LC_MESSAGES/django.po in es (#12354)
* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

* Translate locale/en/LC_MESSAGES/django.po in es

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

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2024-12-17 12:57:50 +01:00
83219ff2ca core: bump github.com/go-ldap/ldap/v3 from 3.4.8 to 3.4.9 (#12355)
Bumps [github.com/go-ldap/ldap/v3](https://github.com/go-ldap/ldap) from 3.4.8 to 3.4.9.
- [Release notes](https://github.com/go-ldap/ldap/releases)
- [Commits](https://github.com/go-ldap/ldap/compare/v3.4.8...v3.4.9)

---
updated-dependencies:
- dependency-name: github.com/go-ldap/ldap/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-17 12:57:23 +01:00
be3e01912d core: bump debugpy from 1.8.10 to 1.8.11 (#12356)
Bumps [debugpy](https://github.com/microsoft/debugpy) from 1.8.10 to 1.8.11.
- [Release notes](https://github.com/microsoft/debugpy/releases)
- [Commits](https://github.com/microsoft/debugpy/compare/v1.8.10...v1.8.11)

---
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-17 12:56:15 +01:00
0e180ebd21 ci: bump helm/kind-action from 1.10.0 to 1.11.0 (#12361)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  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-17 12:40:27 +01:00
190cb33f8e website/docs: reverse proxies: add backlink to trusted_proxy_cidrs config (#12364) 2024-12-17 12:21:04 +01:00
15061dab6d website/integrations: proxmox: cleanup guide (#11894)
* website/integrations: proxmox: clarify location of Providers tab

Updates the guide to explicitly state the path to the Providers tab.

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

* website/integrations: proxmox: cleanup instructions

Cleans up instructions to make them more readable


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

* website/integrations: proxmox: specify application tab location

Specifies the location of the Applications tab in the admin interface

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

* website/integrations: proxmox remove duplicate instructions

Some instructions were copied over in my previous commit and were never removed. This commit removes redundant instructions

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

* website/integrations: proxmox: try to adhere to style guide

Touch up a few things to try to make it match

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

* website/integrations: proxmox: lint

Lint with prettier

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

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

* website/integrations: proxmox: address review comment

Address review comment https://github.com/goauthentik/authentik/pull/11894#discussion_r1837106698 - Merge lines 42 and 44

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

* website/integrations: proxmox: lint

run `npx prettier --write website/integrations/services/proxmox-ve/index.md`

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

* Update website/integrations/services/proxmox-ve/index.md

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: 4d62 <github-user@sdko.org>

---------

Signed-off-by: 4d62 <github-user@sdko.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-12-16 13:24:38 -06:00
d5a7f0fc3a CODEOWNERS: add SECURITY.md (#12360) 2024-12-16 17:08:34 +00:00
c1525449cf website: bump aws-cdk from 2.173.0 to 2.173.1 in /website (#12357)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-16 17:48:30 +01:00
f23965a55e root: SECURITY.md: fix markdown for Security documentation link (#12353) 2024-12-16 17:44:40 +01:00
de5191be6c web: bump API Client version (#12342)
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-16 17:28:11 +01:00
0bc2d4a7b8 website/integrations: Update Home Assistant integration docs (#12233)
Update Home Assistant integration docs

Extra care should be taken to proxy only /auth endpoint through Authentik.

Signed-off-by: Sergii Bogomolov <sergii@bogomolov.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
2024-12-16 14:54:14 +00:00
4b5e66f9eb website/docs: add 2024.12+ badge to redirect stage features (#12346)
add `2024.12+` badge to redirect stage features
2024-12-13 01:11:29 +00:00
9a5effae2c root: fix ssl settings for read replicas not being applied (#12341)
* root: fix ssl settings for read replicas not being applied

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

* fix

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

* slight refactor

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-12 18:53:52 +01:00
ff504a3b80 stages/redirect: create redirect stage (#12275)
* create redirect stage

* show "keep context" toggle in Flow mode only

* fix typos

* add docs

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>

* simplify property pass

* simplify toggle

* remove `print` statements

whoops

* fix typo

* remove default from `RedirectStage.mode`

* remove migration

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

* oops

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

* adjust docs

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2024-12-12 18:00:09 +01:00
587f2d74ac web/flows: update flow background (#12339)
* web/flows: update flow background

https://unsplash.com/photos/a-snowy-road-with-trees-and-mountains-in-the-background-sUeF3oM_UB8
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Optimised images with calibre/image-actions

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-12-12 17:51:30 +01:00
c3555c778c web: bump API Client version (#12336)
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-12 13:26:58 +00:00
1acf48ae1e core: bump golang.org/x/crypto from 0.25.0 to 0.31.0 (#12335)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.25.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.25.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  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-12 14:18:39 +01:00
a32d396cec website: bump prism-react-renderer from 2.4.0 to 2.4.1 in /website (#12329)
Bumps [prism-react-renderer](https://github.com/FormidableLabs/prism-react-renderer) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/FormidableLabs/prism-react-renderer/releases)
- [Commits](https://github.com/FormidableLabs/prism-react-renderer/compare/prism-react-renderer@2.4.0...prism-react-renderer@2.4.1)

---
updated-dependencies:
- dependency-name: prism-react-renderer
  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-12 13:57:21 +01:00
deacc17832 sources/kerberos: add kadmin type setting, provide additional context to property mappings (#12286) 2024-12-12 13:25:43 +01:00
96b3e2b3d9 website: bump aws-cdk from 2.172.0 to 2.173.0 in /website (#12330)
Bumps [aws-cdk](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk) from 2.172.0 to 2.173.0.
- [Release notes](https://github.com/aws/aws-cdk/releases)
- [Changelog](https://github.com/aws/aws-cdk/blob/v2.173.0/CHANGELOG.v2.md)
- [Commits](https://github.com/aws/aws-cdk/commits/v2.173.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-12 13:19:06 +01:00
ddd3b0557e website/docs: fix typo in device code auth example (#12331) 2024-12-12 09:47:57 +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
647 changed files with 45298 additions and 32313 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

@ -35,7 +35,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
poetry install
poetry install --sync
cd web && npm ci
- name: Generate config
shell: poetry run python {0}

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

@ -134,7 +134,7 @@ jobs:
- name: Setup authentik env
uses: ./.github/actions/setup
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.10.0
uses: helm/kind-action@v1.11.0
- name: run integration
run: |
poetry run coverage run manage.py test tests/integration
@ -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

@ -29,11 +29,13 @@
"!Enumerate sequence",
"!Env scalar",
"!Find sequence",
"!FindObject sequence",
"!Format sequence",
"!If sequence",
"!Index scalar",
"!KeyOf scalar",
"!Value scalar"
"!Value scalar",
"!AtIndex scalar"
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
@ -50,7 +52,9 @@
"ignoreCase": false
}
],
"go.testFlags": ["-count=1"],
"go.testFlags": [
"-count=1"
],
"github-actions.workflows.pinned.workflows": [
".github/workflows/ci-main.yml"
]

View File

@ -19,10 +19,18 @@ Dockerfile @goauthentik/infrastructure
*Dockerfile @goauthentik/infrastructure
.dockerignore @goauthentik/infrastructure
docker-compose.yml @goauthentik/infrastructure
Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
# Web
web/ @goauthentik/frontend
tests/wdio/ @goauthentik/frontend
# Locale
locale/ @goauthentik/backend @goauthentik/frontend
web/xliff/ @goauthentik/backend @goauthentik/frontend
# Docs & Website
website/ @goauthentik/docs
CODE_OF_CONDUCT.md @goauthentik/docs
# Security
website/docs/security/ @goauthentik/security
SECURITY.md @goauthentik/security @goauthentik/docs
website/docs/security/ @goauthentik/security @goauthentik/docs

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

@ -146,6 +146,11 @@ entries:
]
]
nested_context: !Context context2
at_index_sequence: !AtIndex [!Context sequence, 0]
at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"]
at_index_mapping: !AtIndex [!Context mapping, "key2"]
at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"]
find_object: !AtIndex [!FindObject [authentik_providers_oauth2.scopemapping, [scope_name, openid]], managed]
identifiers:
name: test
conditions:

View File

@ -4,6 +4,7 @@ from os import environ
from django.test import TransactionTestCase
from authentik.blueprints.tests import apply_blueprint
from authentik.blueprints.v1.exporter import FlowExporter
from authentik.blueprints.v1.importer import Importer, transaction_rollback
from authentik.core.models import Group
@ -126,6 +127,7 @@ class TestBlueprintsV1(TransactionTestCase):
self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
@apply_blueprint("system/providers-oauth2.yaml")
def test_import_yaml_tags(self):
"""Test some yaml tags"""
ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete()
@ -136,87 +138,93 @@ class TestBlueprintsV1(TransactionTestCase):
self.assertTrue(importer.apply())
policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first()
self.assertTrue(policy)
self.assertTrue(
Group.objects.filter(
attributes={
"policy_pk1": str(policy.pk) + "-suffix",
"policy_pk2": str(policy.pk) + "-suffix",
"boolAnd": True,
"boolNand": False,
"boolOr": True,
"boolNor": False,
"boolXor": True,
"boolXnor": False,
"boolComplex": True,
"if_true_complex": {
"dictionary": {
"with": {"keys": "and_values"},
"and_nested_custom_tags": "foo-bar",
}
group = Group.objects.filter(name="test").first()
self.assertIsNotNone(group)
self.assertEqual(
group.attributes,
{
"policy_pk1": str(policy.pk) + "-suffix",
"policy_pk2": str(policy.pk) + "-suffix",
"boolAnd": True,
"boolNand": False,
"boolOr": True,
"boolNor": False,
"boolXor": True,
"boolXnor": False,
"boolComplex": True,
"if_true_complex": {
"dictionary": {
"with": {"keys": "and_values"},
"and_nested_custom_tags": "foo-bar",
}
},
"if_false_complex": ["list", "with", "items", "foo-bar"],
"if_true_simple": True,
"if_short": True,
"if_false_simple": 2,
"enumerate_mapping_to_mapping": {
"prefix-key1": "other-prefix-value",
"prefix-key2": "other-prefix-2",
},
"enumerate_mapping_to_sequence": [
"prefixed-pair-key1-value",
"prefixed-pair-key2-2",
],
"enumerate_sequence_to_sequence": [
"prefixed-items-0-foo",
"prefixed-items-1-bar",
],
"enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
"nested_complex_enumeration": {
"0": {
"key1": [
["prefixed-f", "prefixed-o", "prefixed-o"],
{
"outer_value": "foo",
"outer_index": 0,
"middle_value": "value",
"middle_index": "key1",
},
],
"key2": [
["prefixed-f", "prefixed-o", "prefixed-o"],
{
"outer_value": "foo",
"outer_index": 0,
"middle_value": 2,
"middle_index": "key2",
},
],
},
"if_false_complex": ["list", "with", "items", "foo-bar"],
"if_true_simple": True,
"if_short": True,
"if_false_simple": 2,
"enumerate_mapping_to_mapping": {
"prefix-key1": "other-prefix-value",
"prefix-key2": "other-prefix-2",
"1": {
"key1": [
["prefixed-b", "prefixed-a", "prefixed-r"],
{
"outer_value": "bar",
"outer_index": 1,
"middle_value": "value",
"middle_index": "key1",
},
],
"key2": [
["prefixed-b", "prefixed-a", "prefixed-r"],
{
"outer_value": "bar",
"outer_index": 1,
"middle_value": 2,
"middle_index": "key2",
},
],
},
"enumerate_mapping_to_sequence": [
"prefixed-pair-key1-value",
"prefixed-pair-key2-2",
],
"enumerate_sequence_to_sequence": [
"prefixed-items-0-foo",
"prefixed-items-1-bar",
],
"enumerate_sequence_to_mapping": {"index: 0": "foo", "index: 1": "bar"},
"nested_complex_enumeration": {
"0": {
"key1": [
["prefixed-f", "prefixed-o", "prefixed-o"],
{
"outer_value": "foo",
"outer_index": 0,
"middle_value": "value",
"middle_index": "key1",
},
],
"key2": [
["prefixed-f", "prefixed-o", "prefixed-o"],
{
"outer_value": "foo",
"outer_index": 0,
"middle_value": 2,
"middle_index": "key2",
},
],
},
"1": {
"key1": [
["prefixed-b", "prefixed-a", "prefixed-r"],
{
"outer_value": "bar",
"outer_index": 1,
"middle_value": "value",
"middle_index": "key1",
},
],
"key2": [
["prefixed-b", "prefixed-a", "prefixed-r"],
{
"outer_value": "bar",
"outer_index": 1,
"middle_value": 2,
"middle_index": "key2",
},
],
},
},
"nested_context": "context-nested-value",
"env_null": None,
}
).exists()
},
"nested_context": "context-nested-value",
"env_null": None,
"at_index_sequence": "foo",
"at_index_sequence_default": "non existent",
"at_index_mapping": 2,
"at_index_mapping_default": "non existent",
"find_object": "goauthentik.io/providers/oauth2/scope-openid",
},
)
self.assertTrue(
OAuthSource.objects.filter(

View File

@ -24,6 +24,10 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.models import PolicyBindingModel
class UNSET:
"""Used to test whether a key has not been set."""
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and convert it to a normal dict"""
serializer: Serializer = obj.serializer(obj)
@ -307,7 +311,7 @@ class Format(YAMLTag):
class Find(YAMLTag):
"""Find any object"""
"""Find any object primary key"""
model_name: str | YAMLTag
conditions: list[list]
@ -322,7 +326,7 @@ class Find(YAMLTag):
values.append(loader.construct_object(node_values))
self.conditions.append(values)
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
def _get_instance(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
if isinstance(self.model_name, YAMLTag):
model_name = self.model_name.resolve(entry, blueprint)
else:
@ -344,12 +348,29 @@ class Find(YAMLTag):
else:
query_value = cond[1]
query &= Q(**{query_key: query_value})
instance = model_class.objects.filter(query).first()
return model_class.objects.filter(query).first()
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
instance = self._get_instance(entry, blueprint)
if instance:
return instance.pk
return None
class FindObject(Find):
"""Find any object"""
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
instance = self._get_instance(entry, blueprint)
if not instance:
return None
if not isinstance(instance, SerializerModel):
raise EntryInvalidError.from_entry(
f"Model {self.model_name} is not resolvable through FindObject", entry
)
return instance.serializer(instance=instance).data
class Condition(YAMLTag):
"""Convert all values to a single boolean"""
@ -556,6 +577,53 @@ class Value(EnumeratedItem):
raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc
class AtIndex(YAMLTag):
"""Get value at index of a sequence or mapping"""
obj: YAMLTag | dict | list | tuple
attribute: int | str | YAMLTag
default: Any | UNSET
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
super().__init__()
self.obj = loader.construct_object(node.value[0])
self.attribute = loader.construct_object(node.value[1])
if len(node.value) == 2: # noqa: PLR2004
self.default = UNSET
else:
self.default = loader.construct_object(node.value[2])
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
if isinstance(self.obj, YAMLTag):
obj = self.obj.resolve(entry, blueprint)
else:
obj = self.obj
if isinstance(self.attribute, YAMLTag):
attribute = self.attribute.resolve(entry, blueprint)
else:
attribute = self.attribute
if isinstance(obj, list | tuple):
try:
return obj[attribute]
except TypeError as exc:
raise EntryInvalidError.from_entry(
f"Invalid index for list: {attribute}", entry
) from exc
except IndexError as exc:
if self.default is UNSET:
raise EntryInvalidError.from_entry(
f"Index out of range: {attribute}", entry
) from exc
return self.default
if attribute in obj:
return obj[attribute]
else:
if self.default is UNSET:
raise EntryInvalidError.from_entry(f"Key does not exist: {attribute}", entry)
return self.default
class BlueprintDumper(SafeDumper):
"""Dump dataclasses to yaml"""
@ -598,6 +666,7 @@ class BlueprintLoader(SafeLoader):
super().__init__(*args, **kwargs)
self.add_constructor("!KeyOf", KeyOf)
self.add_constructor("!Find", Find)
self.add_constructor("!FindObject", FindObject)
self.add_constructor("!Context", Context)
self.add_constructor("!Format", Format)
self.add_constructor("!Condition", Condition)
@ -606,6 +675,7 @@ class BlueprintLoader(SafeLoader):
self.add_constructor("!Enumerate", Enumerate)
self.add_constructor("!Value", Value)
self.add_constructor("!Index", Index)
self.add_constructor("!AtIndex", AtIndex)
class EntryInvalidError(SentryIgnoredException):

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

@ -0,0 +1,54 @@
"""Application Roles API Viewset"""
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import (
Application,
ApplicationEntitlement,
User,
)
class ApplicationEntitlementSerializer(ModelSerializer):
"""ApplicationEntitlement Serializer"""
def validate_app(self, app: Application) -> Application:
"""Ensure user has permission to view"""
user: User = self._context["request"].user
if user.has_perm("view_application", app) or user.has_perm(
"authentik_core.view_application"
):
return app
raise ValidationError(_("User does not have access to application."), code="invalid")
class Meta:
model = ApplicationEntitlement
fields = [
"pbm_uuid",
"name",
"app",
"attributes",
]
class ApplicationEntitlementViewSet(UsedByMixin, ModelViewSet):
"""ApplicationEntitlement Viewset"""
queryset = ApplicationEntitlement.objects.all()
serializer_class = ApplicationEntitlementSerializer
search_fields = [
"pbm_uuid",
"name",
"app",
"attributes",
]
filterset_fields = [
"pbm_uuid",
"name",
"app",
]
ordering = ["name"]

View File

@ -159,9 +159,9 @@ class SourceViewSet(
class UserSourceConnectionSerializer(SourceSerializer):
"""OAuth Source Serializer"""
"""User source connection"""
source = SourceSerializer(read_only=True)
source_obj = SourceSerializer(read_only=True, source="source")
class Meta:
model = UserSourceConnection
@ -169,10 +169,10 @@ class UserSourceConnectionSerializer(SourceSerializer):
"pk",
"user",
"source",
"source_obj",
"created",
]
extra_kwargs = {
"user": {"read_only": True},
"created": {"read_only": True},
}
@ -197,9 +197,9 @@ class UserSourceConnectionViewSet(
class GroupSourceConnectionSerializer(SourceSerializer):
"""Group Source Connection Serializer"""
"""Group Source Connection"""
source = SourceSerializer(read_only=True)
source_obj = SourceSerializer(read_only=True)
class Meta:
model = GroupSourceConnection
@ -207,12 +207,11 @@ class GroupSourceConnectionSerializer(SourceSerializer):
"pk",
"group",
"source",
"source_obj",
"identifier",
"created",
]
extra_kwargs = {
"group": {"read_only": True},
"identifier": {"read_only": True},
"created": {"read_only": True},
}

View File

@ -22,7 +22,7 @@ from authentik.blueprints.v1.common import (
from authentik.blueprints.v1.importer import Importer
from authentik.core.api.applications import ApplicationSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Provider
from authentik.core.models import Application, Provider
from authentik.lib.utils.reflection import all_subclasses
from authentik.policies.api.bindings import PolicyBindingSerializer
@ -51,6 +51,13 @@ class TransactionProviderField(DictField):
class TransactionPolicyBindingSerializer(PolicyBindingSerializer):
"""PolicyBindingSerializer which does not require target as target is set implicitly"""
def validate(self, attrs):
# As the PolicyBindingSerializer checks that the correct things can be bound to a target
# but we don't have a target here as that's set by the blueprint, pass in an empty app
# which will have the correct allowed combination of group/user/policy.
attrs["target"] = Application()
return super().validate(attrs)
class Meta(PolicyBindingSerializer.Meta):
fields = [x for x in PolicyBindingSerializer.Meta.fields if x != "target"]

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,45 @@
# Generated by Django 5.0.9 on 2024-11-20 15:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0040_provider_invalidation_flow"),
("authentik_policies", "0011_policybinding_failure_result_and_more"),
]
operations = [
migrations.CreateModel(
name="ApplicationEntitlement",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policybindingmodel",
),
),
("attributes", models.JSONField(blank=True, default=dict)),
("name", models.TextField()),
(
"app",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_core.application"
),
),
],
options={
"verbose_name": "Application Entitlement",
"verbose_name_plural": "Application Entitlements",
"unique_together": {("app", "name")},
},
bases=("authentik_policies.policybindingmodel", models.Model),
),
]

View File

@ -314,6 +314,32 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
always_merger.merge(final_attributes, self.attributes)
return final_attributes
def app_entitlements(self, app: "Application | None") -> QuerySet["ApplicationEntitlement"]:
"""Get all entitlements this user has for `app`."""
if not app:
return []
all_groups = self.all_groups()
qs = app.applicationentitlement_set.filter(
Q(
Q(bindings__user=self) | Q(bindings__group__in=all_groups),
bindings__negate=False,
)
| Q(
Q(~Q(bindings__user=self), bindings__user__isnull=False)
| Q(~Q(bindings__group__in=all_groups), bindings__group__isnull=False),
bindings__negate=True,
),
bindings__enabled=True,
).order_by("name")
return qs
def app_entitlements_attributes(self, app: "Application | None") -> dict:
"""Get a dictionary containing all merged attributes from app entitlements for `app`."""
final_attributes = {}
for attrs in self.app_entitlements(app).values_list("attributes", flat=True):
always_merger.merge(final_attributes, attrs)
return final_attributes
@property
def serializer(self) -> Serializer:
from authentik.core.api.users import UserSerializer
@ -581,6 +607,31 @@ class Application(SerializerModel, PolicyBindingModel):
verbose_name_plural = _("Applications")
class ApplicationEntitlement(AttributesMixin, SerializerModel, PolicyBindingModel):
"""Application-scoped entitlement to control authorization in an application"""
name = models.TextField()
app = models.ForeignKey(Application, on_delete=models.CASCADE)
class Meta:
verbose_name = _("Application Entitlement")
verbose_name_plural = _("Application Entitlements")
unique_together = (("app", "name"),)
def __str__(self):
return f"Application Entitlement {self.name} for app {self.app_id}"
@property
def serializer(self) -> type[Serializer]:
from authentik.core.api.application_entitlements import ApplicationEntitlementSerializer
return ApplicationEntitlementSerializer
def supported_policy_binding_targets(self):
return ["group", "user"]
class SourceUserMatchingModes(models.TextChoices):
"""Different modes a source can handle new/returning users"""

View File

@ -238,13 +238,7 @@ class SourceFlowManager:
self.request.GET,
flow_slug=flow_slug,
)
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
if PLAN_CONTEXT_REDIRECT not in flow_context:
flow_context[PLAN_CONTEXT_REDIRECT] = final_redirect
flow_context.setdefault(PLAN_CONTEXT_REDIRECT, final_redirect)
if not flow:
return bad_request_message(
@ -265,12 +259,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

@ -0,0 +1,153 @@
"""Test Application Entitlements API"""
from django.urls import reverse
from guardian.shortcuts import assign_perm
from rest_framework.test import APITestCase
from authentik.core.models import Application, ApplicationEntitlement, Group
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
from authentik.lib.generators import generate_id
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import OAuth2Provider
class TestApplicationEntitlements(APITestCase):
"""Test application entitlements"""
def setUp(self) -> None:
self.user = create_test_user()
self.other_user = create_test_user()
self.provider = OAuth2Provider.objects.create(
name="test",
authorization_flow=create_test_flow(),
)
self.app: Application = Application.objects.create(
name=generate_id(),
slug=generate_id(),
provider=self.provider,
)
def test_user(self):
"""Test user-direct assignment"""
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, user=self.user, order=0)
ents = self.user.app_entitlements(self.app)
self.assertEqual(len(ents), 1)
self.assertEqual(ents[0].name, ent.name)
def test_group(self):
"""Test direct group"""
group = Group.objects.create(name=generate_id())
self.user.ak_groups.add(group)
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, group=group, order=0)
ents = self.user.app_entitlements(self.app)
self.assertEqual(len(ents), 1)
self.assertEqual(ents[0].name, ent.name)
def test_group_indirect(self):
"""Test indirect group"""
parent = Group.objects.create(name=generate_id())
group = Group.objects.create(name=generate_id(), parent=parent)
self.user.ak_groups.add(group)
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, group=parent, order=0)
ents = self.user.app_entitlements(self.app)
self.assertEqual(len(ents), 1)
self.assertEqual(ents[0].name, ent.name)
def test_negate_user(self):
"""Test with negate flag"""
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, user=self.other_user, order=0, negate=True)
ents = self.user.app_entitlements(self.app)
self.assertEqual(len(ents), 1)
self.assertEqual(ents[0].name, ent.name)
def test_negate_group(self):
"""Test with negate flag"""
other_group = Group.objects.create(name=generate_id())
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
PolicyBinding.objects.create(target=ent, group=other_group, order=0, negate=True)
ents = self.user.app_entitlements(self.app)
self.assertEqual(len(ents), 1)
self.assertEqual(ents[0].name, ent.name)
def test_api_perms_global(self):
"""Test API creation with global permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
assign_perm("authentik_core.view_application", self.user)
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),
data={
"name": generate_id(),
"app": self.app.pk,
},
)
self.assertEqual(res.status_code, 201)
def test_api_perms_scoped(self):
"""Test API creation with scoped permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
assign_perm("authentik_core.view_application", self.user, self.app)
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),
data={
"name": generate_id(),
"app": self.app.pk,
},
)
self.assertEqual(res.status_code, 201)
def test_api_perms_missing(self):
"""Test API creation with no permissions"""
assign_perm("authentik_core.add_applicationentitlement", self.user)
self.client.force_login(self.user)
res = self.client.post(
reverse("authentik_api:applicationentitlement-list"),
data={
"name": generate_id(),
"app": self.app.pk,
},
)
self.assertEqual(res.status_code, 400)
self.assertJSONEqual(res.content, {"app": ["User does not have access to application."]})
def test_api_bindings_policy(self):
"""Test that API doesn't allow policies to be bound to this"""
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
policy = DummyPolicy.objects.create(name=generate_id())
admin = create_test_admin_user()
self.client.force_login(admin)
response = self.client.post(
reverse("authentik_api:policybinding-list"),
data={
"target": ent.pbm_uuid,
"policy": policy.pk,
"order": 0,
},
)
self.assertJSONEqual(
response.content.decode(),
{"non_field_errors": ["One of 'group', 'user' must be set."]},
)
def test_api_bindings_group(self):
"""Test that API doesn't allow policies to be bound to this"""
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
group = Group.objects.create(name=generate_id())
admin = create_test_admin_user()
self.client.force_login(admin)
response = self.client.post(
reverse("authentik_api:policybinding-list"),
data={
"target": ent.pbm_uuid,
"group": group.pk,
"order": 0,
},
)
self.assertEqual(response.status_code, 201)
self.assertTrue(PolicyBinding.objects.filter(target=ent.pbm_uuid).exists())

View File

@ -6,6 +6,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.urls import path
from authentik.core.api.application_entitlements import ApplicationEntitlementViewSet
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
@ -69,6 +70,7 @@ urlpatterns = [
api_urlpatterns = [
("core/authenticated_sessions", AuthenticatedSessionViewSet),
("core/applications", ApplicationViewSet),
("core/application_entitlements", ApplicationEntitlementViewSet),
path(
"core/transactional/applications/",
TransactionalApplicationView.as_view(),

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

@ -40,6 +40,7 @@ class Migration(migrations.Migration):
("require_authenticated", "Require Authenticated"),
("require_unauthenticated", "Require Unauthenticated"),
("require_superuser", "Require Superuser"),
("require_redirect", "Require Redirect"),
("require_outpost", "Require Outpost"),
],
default="none",

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
@ -32,6 +33,7 @@ class FlowAuthenticationRequirement(models.TextChoices):
REQUIRE_AUTHENTICATED = "require_authenticated"
REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
REQUIRE_SUPERUSER = "require_superuser"
REQUIRE_REDIRECT = "require_redirect"
REQUIRE_OUTPOST = "require_outpost"
@ -177,9 +179,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"
@ -37,6 +42,8 @@ PLAN_CONTEXT_OUTPOST = "outpost"
# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan
# was restored.
PLAN_CONTEXT_IS_RESTORED = "is_restored"
PLAN_CONTEXT_IS_REDIRECTED = "is_redirected"
PLAN_CONTEXT_REDIRECT_STAGE_TARGET = "redirect_stage_target"
CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_flows")
CACHE_PREFIX = "goauthentik.io/flows/planner/"
@ -110,6 +117,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
@ -128,7 +183,7 @@ class FlowPlanner:
self.flow = flow
self._logger = get_logger().bind(flow_slug=flow.slug)
def _check_authentication(self, request: HttpRequest):
def _check_authentication(self, request: HttpRequest, context: dict[str, Any]):
"""Check the flow's authentication level is matched by `request`"""
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
@ -145,6 +200,11 @@ class FlowPlanner:
and not request.user.is_superuser
):
raise FlowNonApplicableException()
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_REDIRECT
and context.get(PLAN_CONTEXT_IS_REDIRECTED) is None
):
raise FlowNonApplicableException()
outpost_user = ClientIPMiddleware.get_outpost_user(request)
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
if not outpost_user:
@ -176,18 +236,13 @@ class FlowPlanner:
)
context = default_context or {}
# Bit of a workaround here, if there is a pending user set in the default context
# we use that user for our cache key
# to make sure they don't get the generic response
# we use that user for our cache key to make sure they don't get the generic response
if context and PLAN_CONTEXT_PENDING_USER in context:
user = context[PLAN_CONTEXT_PENDING_USER]
else:
user = request.user
# We only need to check the flow authentication if it's planned without a user
# in the context, as a user in the context can only be set via the explicit code API
# or if a flow is restarted due to `invalid_response_action` being set to
# `restart_with_context`, which can only happen if the user was already authorized
# to use the flow
context.update(self._check_authentication(request))
context.update(self._check_authentication(request, context))
# First off, check the flow's direct policy bindings
# to make sure the user even has access to the flow
engine = PolicyEngine(self.flow, user, request)

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
@ -92,7 +93,11 @@ class ChallengeStageView(StageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Return a challenge for the frontend to solve"""
challenge = self._get_challenge(*args, **kwargs)
try:
challenge = self._get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
if not challenge.is_valid():
self.logger.warning(
"f(ch): Invalid challenge",
@ -168,11 +173,7 @@ class ChallengeStageView(StageView):
stage_type=self.__class__.__name__, method="get_challenge"
).time(),
):
try:
challenge = self.get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
challenge = self.get_challenge(*args, **kwargs)
with start_span(
op="authentik.flow.stage._get_challenge",
name=self.__class__.__name__,
@ -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,19 @@ 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.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.flows.models import (
FlowAuthenticationRequirement,
FlowDesignation,
FlowStageBinding,
in_memory_stage,
)
from authentik.flows.planner import (
PLAN_CONTEXT_IS_REDIRECTED,
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
@ -73,6 +86,24 @@ class TestFlowPlanner(TestCase):
planner.allow_empty_flows = True
planner.plan(request)
def test_authentication_redirect_required(self):
"""Test flow authentication (redirect required)"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.REQUIRE_REDIRECT
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
with self.assertRaises(FlowNonApplicableException):
planner.plan(request)
context = {}
context[PLAN_CONTEXT_IS_REDIRECTED] = create_test_flow()
planner.plan(request, context)
@reconcile_app("authentik_outposts")
def test_authentication_outpost(self):
"""Test flow authentication (outpost)"""
@ -211,3 +242,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

@ -171,7 +171,8 @@ class FlowExecutorView(APIView):
# Existing plan is deleted from session and instance
self.plan = None
self.cancel()
self._logger.debug("f(exec): Continuing existing plan")
else:
self._logger.debug("f(exec): Continuing existing plan")
# Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = get_params
@ -597,9 +598,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

@ -5,6 +5,7 @@ import json
import os
from collections.abc import Mapping
from contextlib import contextmanager
from copy import deepcopy
from dataclasses import dataclass, field
from enum import Enum
from glob import glob
@ -336,6 +337,58 @@ def redis_url(db: int) -> str:
return _redis_url
def django_db_config(config: ConfigLoader | None = None) -> dict:
if not config:
config = CONFIG
db = {
"default": {
"ENGINE": "authentik.root.db",
"HOST": config.get("postgresql.host"),
"NAME": config.get("postgresql.name"),
"USER": config.get("postgresql.user"),
"PASSWORD": config.get("postgresql.password"),
"PORT": config.get("postgresql.port"),
"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"),
},
}
}
if config.get_bool("postgresql.use_pgpool", False):
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
if config.get_bool("postgresql.use_pgbouncer", False):
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
db["default"]["CONN_MAX_AGE"] = None # persistent
for replica in config.get_keys("postgresql.read_replicas"):
_database = deepcopy(db["default"])
for setting, current_value in db["default"].items():
if isinstance(current_value, dict):
continue
override = config.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
)
if override is not UNSET:
_database[setting] = override
for setting in db["default"]["OPTIONS"].keys():
override = config.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
)
if override is not UNSET:
_database["OPTIONS"][setting] = override
db[f"replica_{replica}"] = _database
return db
if __name__ == "__main__":
if len(argv) < 2: # noqa: PLR2004
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))

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

@ -9,7 +9,14 @@ from unittest import mock
from django.conf import ImproperlyConfigured
from django.test import TestCase
from authentik.lib.config import ENV_PREFIX, UNSET, Attr, AttrEncoder, ConfigLoader
from authentik.lib.config import (
ENV_PREFIX,
UNSET,
Attr,
AttrEncoder,
ConfigLoader,
django_db_config,
)
class TestConfig(TestCase):
@ -175,3 +182,201 @@ class TestConfig(TestCase):
config = ConfigLoader()
config.set("foo.bar", "baz")
self.assertEqual(list(config.get_keys("foo")), ["bar"])
def test_db_default(self):
"""Test default DB Config"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
}
},
)
def test_db_read_replicas(self):
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)
def test_db_read_replicas_pgpool(self):
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
config.set("postgresql.use_pgpool", True)
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
# This isn't supported
config.set("postgresql.read_replicas.0.use_pgpool", False)
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"DISABLE_SERVER_SIDE_CURSORS": True,
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"DISABLE_SERVER_SIDE_CURSORS": True,
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)
def test_db_read_replicas_diff_ssl(self):
"""Test read replicas (with different SSL Settings)"""
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
config.set("postgresql.read_replicas.0.sslcert", "bar")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "bar",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)

View File

@ -84,19 +84,17 @@ class PolicyBindingSerializer(ModelSerializer):
def validate(self, attrs: OrderedDict) -> OrderedDict:
"""Check that either policy, group or user is set."""
count = sum(
[
bool(attrs.get("policy", None)),
bool(attrs.get("group", None)),
bool(attrs.get("user", None)),
]
)
target: PolicyBindingModel = attrs.get("target")
supported = target.supported_policy_binding_targets()
supported.sort()
count = sum([bool(attrs.get(x, None)) for x in supported])
invalid = count > 1
empty = count < 1
warning = ", ".join(f"'{x}'" for x in supported)
if invalid:
raise ValidationError("Only one of 'policy', 'group' or 'user' can be set.")
raise ValidationError(f"Only one of {warning} can be set.")
if empty:
raise ValidationError("One of 'policy', 'group' or 'user' must be set.")
raise ValidationError(f"One of {warning} must be set.")
return attrs

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

@ -1,4 +1,6 @@
# Generated by Django 4.2.5 on 2023-09-13 18:07
import authentik.lib.models
import django.db.models.deletion
from django.db import migrations, models
@ -23,4 +25,13 @@ class Migration(migrations.Migration):
default=30, help_text="Timeout after which Policy execution is terminated."
),
),
migrations.AlterField(
model_name="policybinding",
name="target",
field=authentik.lib.models.InheritanceForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="bindings",
to="authentik_policies.policybindingmodel",
),
),
]

View File

@ -47,6 +47,10 @@ class PolicyBindingModel(models.Model):
def __str__(self) -> str:
return f"PolicyBindingModel {self.pbm_uuid}"
def supported_policy_binding_targets(self):
"""Return the list of objects that can be bound to this object."""
return ["policy", "user", "group"]
class PolicyBinding(SerializerModel):
"""Relationship between a Policy and a PolicyBindingModel."""
@ -81,7 +85,9 @@ class PolicyBinding(SerializerModel):
blank=True,
)
target = InheritanceForeignKey(PolicyBindingModel, on_delete=models.CASCADE, related_name="+")
target = InheritanceForeignKey(
PolicyBindingModel, on_delete=models.CASCADE, related_name="bindings"
)
negate = models.BooleanField(
default=False,
help_text=_("Negates the outcome of the policy. Messages are unaffected."),

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

@ -38,7 +38,7 @@ class TestBindingsAPI(APITestCase):
)
self.assertJSONEqual(
response.content.decode(),
{"non_field_errors": ["Only one of 'policy', 'group' or 'user' can be set."]},
{"non_field_errors": ["Only one of 'group', 'policy', 'user' can be set."]},
)
def test_invalid_too_little(self):
@ -49,5 +49,5 @@ class TestBindingsAPI(APITestCase):
)
self.assertJSONEqual(
response.content.decode(),
{"non_field_errors": ["One of 'policy', 'group' or 'user' must be set."]},
{"non_field_errors": ["One of 'group', 'policy', 'user' must be set."]},
)

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

@ -0,0 +1,38 @@
# Generated by Django 5.0.10 on 2024-12-12 17:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0040_provider_invalidation_flow"),
(
"authentik_providers_oauth2",
"0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more",
),
]
operations = [
migrations.AlterField(
model_name="accesstoken",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authentik_core.authenticatedsession",
),
),
migrations.AlterField(
model_name="authorizationcode",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authentik_core.authenticatedsession",
),
),
]

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]:
@ -395,7 +396,7 @@ class BaseGrantModel(models.Model):
_scope = models.TextField(default="", verbose_name=_("Scopes"))
auth_time = models.DateTimeField(verbose_name="Authentication time")
session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
AuthenticatedSession, null=True, on_delete=models.CASCADE, default=None
)
class Meta:
@ -496,6 +497,11 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
token = models.TextField(default=generate_client_secret)
_id_token = models.TextField(verbose_name=_("ID Token"))
# Shadow the `session` field from `BaseGrantModel` as we want refresh tokens to persist even
# when the session is terminated.
session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
)
class Meta:
indexes = [

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

@ -127,6 +127,7 @@ class Traefik3MiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]
authResponseHeaders=[
"X-authentik-username",
"X-authentik-groups",
"X-authentik-entitlements",
"X-authentik-email",
"X-authentik-name",
"X-authentik-uid",

View File

@ -147,6 +147,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-profile",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-entitlements",
"goauthentik.io/providers/proxy/scope-proxy",
]
)

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

@ -12,7 +12,7 @@ from sentry_sdk import set_tag
from xmlsec import enable_debug_trace
from authentik import __version__
from authentik.lib.config import CONFIG, redis_url
from authentik.lib.config import CONFIG, django_db_config, redis_url
from authentik.lib.logging import get_logger_config, structlog_configure
from authentik.lib.sentry import sentry_init
from authentik.lib.utils.reflection import get_env
@ -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",
@ -113,6 +114,7 @@ TENANT_APPS = [
"authentik.stages.invitation",
"authentik.stages.password",
"authentik.stages.prompt",
"authentik.stages.redirect",
"authentik.stages.user_delete",
"authentik.stages.user_login",
"authentik.stages.user_logout",
@ -296,45 +298,7 @@ CHANNEL_LAYERS = {
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
DATABASES = {
"default": {
"ENGINE": "authentik.root.db",
"HOST": CONFIG.get("postgresql.host"),
"NAME": CONFIG.get("postgresql.name"),
"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"),
"TEST": {
"NAME": CONFIG.get("postgresql.test.name"),
},
}
}
if CONFIG.get_bool("postgresql.use_pgpool", False):
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
if CONFIG.get_bool("postgresql.use_pgbouncer", False):
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
DATABASES["default"]["CONN_MAX_AGE"] = None # persistent
for replica in CONFIG.get_keys("postgresql.read_replicas"):
_database = DATABASES["default"].copy()
for setting in DATABASES["default"].keys():
default = object()
if setting in ("TEST",):
continue
override = CONFIG.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=default
)
if override is not default:
_database[setting] = override
DATABASES[f"replica_{replica}"] = _database
DATABASES = django_db_config()
DATABASE_ROUTERS = (
"authentik.tenants.db.FailoverRouter",
@ -425,7 +389,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

@ -32,6 +32,7 @@ class KerberosSourceSerializer(SourceSerializer):
"group_matching_mode",
"realm",
"krb5_conf",
"kadmin_type",
"sync_users",
"sync_users_password",
"sync_principal",
@ -69,6 +70,7 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
"slug",
"enabled",
"realm",
"kadmin_type",
"sync_users",
"sync_users_password",
"sync_principal",

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.10 on 2024-12-06 19:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_kerberos", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="kerberossource",
name="kadmin_type",
field=models.TextField(
choices=[("MIT", "Mit"), ("Heimdal", "Heimdal"), ("other", "Other")],
default="other",
help_text="KAdmin server type",
),
),
]

View File

@ -13,7 +13,7 @@ from django.http import HttpRequest
from django.shortcuts import reverse
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from kadmin import KAdmin
from kadmin import KAdmin, KAdminApiVersion
from kadmin.exceptions import PyKAdminException
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@ -36,6 +36,12 @@ LOGGER = get_logger()
_kadmin_connections: dict[str, Any] = {}
class KAdminType(models.TextChoices):
MIT = "MIT"
HEIMDAL = "Heimdal"
OTHER = "other"
class KerberosSource(Source):
"""Federate Kerberos realm with authentik"""
@ -44,6 +50,9 @@ class KerberosSource(Source):
blank=True,
help_text=_("Custom krb5.conf to use. Uses the system one by default"),
)
kadmin_type = models.TextField(
choices=KAdminType.choices, default=KAdminType.OTHER, help_text=_("KAdmin server type")
)
sync_users = models.BooleanField(
default=False, help_text=_("Sync users from Kerberos into authentik"), db_index=True
@ -199,6 +208,14 @@ class KerberosSource(Source):
return str(conf_path)
def _kadmin_init(self) -> KAdmin | None:
api_version = None
match self.kadmin_type:
case KAdminType.MIT:
api_version = KAdminApiVersion.Version4
case KAdminType.HEIMDAL:
api_version = KAdminApiVersion.Version2
case KAdminType.OTHER:
api_version = KAdminApiVersion.Version2
# kadmin doesn't use a ccache for its connection
# as such, we don't need to create a separate ccache for each source
if not self.sync_principal:
@ -207,6 +224,7 @@ class KerberosSource(Source):
return KAdmin.with_password(
self.sync_principal,
self.sync_password,
api_version=api_version,
)
if self.sync_keytab:
keytab = self.sync_keytab
@ -218,11 +236,13 @@ class KerberosSource(Source):
return KAdmin.with_keytab(
self.sync_principal,
keytab,
api_version=api_version,
)
if self.sync_ccache:
return KAdmin.with_ccache(
self.sync_principal,
self.sync_ccache,
api_version=api_version,
)
return None

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

@ -43,8 +43,10 @@ class KerberosSync:
self._messages = []
self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
self.mapper = SourceMapper(self._source)
self.user_manager = self.mapper.get_manager(User, ["principal"])
self.group_manager = self.mapper.get_manager(Group, ["group_id", "principal"])
self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"])
self.group_manager = self.mapper.get_manager(
Group, ["group_id", "principal", "principal_obj"]
)
self.matcher = SourceMatcher(
self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
)
@ -67,12 +69,16 @@ class KerberosSync:
def _handle_principal(self, principal: str) -> bool:
try:
# TODO: handle permission error
principal_obj = self._connection.get_principal(principal)
defaults = self.mapper.build_object_properties(
object_type=User,
manager=self.user_manager,
user=None,
request=None,
principal=principal,
principal_obj=principal_obj,
)
self._logger.debug("Writing user with attributes", **defaults)
if "username" not in defaults:
@ -91,6 +97,7 @@ class KerberosSync:
request=None,
group_id=group_id,
principal=principal,
principal_obj=principal_obj,
)
for group_id in defaults.pop("groups", [])
}

View File

@ -17,6 +17,7 @@ class TestMetadataProcessor(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(),
issuer="authentik",
signing_kp=create_test_cert(),

View File

@ -28,6 +28,7 @@ class TestPropertyMappings(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,

View File

@ -20,6 +20,7 @@ class TestResponseProcessor(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,

View File

@ -0,0 +1,88 @@
"""SAML Source tests"""
from base64 import b64encode
from django.test import RequestFactory, TestCase
from django.urls import reverse
from authentik.core.tests.utils import create_test_flow
from authentik.flows.planner import PLAN_CONTEXT_REDIRECT, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
from authentik.sources.saml.models import SAMLSource
class TestViews(TestCase):
"""Test SAML Views"""
def setUp(self):
self.factory = RequestFactory()
self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,
pre_authentication_flow=create_test_flow(),
)
def test_enroll(self):
"""Enroll"""
flow = create_test_flow()
self.source.enrollment_flow = flow
self.source.save()
response = self.client.post(
reverse(
"authentik_sources_saml:acs",
kwargs={
"source_slug": self.source.slug,
},
),
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_success.xml").encode()
).decode()
},
)
self.assertEqual(response.status_code, 302)
self.assertRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
)
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
self.assertIsNotNone(plan)
def test_enroll_redirect(self):
"""Enroll when attempting to access a provider"""
initial_redirect = f"http://{generate_id()}"
session = self.client.session
old_plan = FlowPlan(generate_id())
old_plan.context[PLAN_CONTEXT_REDIRECT] = initial_redirect
session[SESSION_KEY_PLAN] = old_plan
session.save()
flow = create_test_flow()
self.source.enrollment_flow = flow
self.source.save()
response = self.client.post(
reverse(
"authentik_sources_saml:acs",
kwargs={
"source_slug": self.source.slug,
},
),
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_success.xml").encode()
).decode()
},
)
self.assertEqual(response.status_code, 302)
self.assertRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
)
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
self.assertIsNotNone(plan)
self.assertEqual(plan.context.get(PLAN_CONTEXT_REDIRECT), initial_redirect)

View File

@ -28,11 +28,11 @@ from authentik.flows.planner import (
PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SOURCE,
PLAN_CONTEXT_SSO,
FlowPlan,
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.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
@ -89,12 +89,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."""
@ -154,12 +149,15 @@ class ACSView(View):
processor = ResponseProcessor(source, request)
try:
processor.parse()
except MissingSAMLResponse as exc:
return bad_request_message(request, str(exc))
except VerificationError as exc:
except (MissingSAMLResponse, VerificationError) as exc:
return bad_request_message(request, str(exc))
try:
if SESSION_KEY_PLAN in request.session:
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
plan_redirect = plan.context.get(PLAN_CONTEXT_REDIRECT)
if plan_redirect:
self.request.session[SESSION_KEY_GET] = {NEXT_ARG_NAME: plan_redirect}
return processor.prepare_flow_manager().get_flow()
except (UnsupportedNameIDFormat, ValueError) as exc:
return bad_request_message(request, str(exc))

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