Compare commits

...

33 Commits

Author SHA1 Message Date
9dc0bb2a77 release: 2022.11.4 2022-12-23 14:17:48 +01:00
debbcb125b web: backport API update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-23 14:17:32 +01:00
2d827eaae1 security: fix CVE 2022 23555 (#4274)
* add flow to invitation

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* show warning on invitation page

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add security advisory

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-23 14:16:30 +01:00
47d79ac28c security: fix CVE 2022 46172 (#4275)
* fallback to current user in user_write, add flag to disable user creation

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* update api and web ui

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* update default flows

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add cve post to website

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-23 14:16:26 +01:00
44bf9a890e release: 2022.11.3 2022-12-02 23:00:59 +02:00
b60c6d4144 web: bump API Client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-02 23:00:45 +02:00
2a4daa5360 release: 2022.11.2 2022-12-01 10:41:29 +02:00
e1a6dede54 *: backport CVE-2022-46145 fix
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-01 10:41:26 +02:00
17ee076f3d root: include security policy in website container
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-30 13:05:38 +02:00
4d12a98c5d root: rework and expand security policy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-30 13:05:35 +02:00
3a13d19695 release: 2022.11.1 2022-11-22 21:42:10 +01:00
ed7bef9dbf blueprints: open fixtures in read only mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 21:39:30 +01:00
4a17795df9 root: fix locales not being included in docker image
closes #3885

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 21:39:26 +01:00
07b1aea767 root: bump security info
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 21:18:02 +01:00
ab0f8d027d website/docs: add 2022.11.1 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 21:17:05 +01:00
b9fdb63a57 core: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 21:02:18 +01:00
94833dd1e7 web/admin: reset cookie_domain when setting non-domain forward auth
closes #4063

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 20:46:20 +01:00
5262d89505 core: fix tab-complete in shell
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 20:30:00 +01:00
ab3d47c437 blueprints: add desired state attribute to objects (#4061)
* add state attribute to delete objects

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tests, move yaml from block to files

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add state to docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* only try to format

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 14:27:20 +01:00
14cd52686d stages/email: add test for email translation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3885
2022-11-22 14:14:42 +01:00
1a39754fe9 *: don't return values in test suites
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-22 11:38:34 +01:00
8599eba863 web: bump @sentry/browser from 7.20.0 to 7.20.1 in /web (#4058)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.20.0 to 7.20.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.20.0...7.20.1)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:09:00 +01:00
4c6d21820e web: bump @typescript-eslint/parser from 5.43.0 to 5.44.0 in /web (#4056)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.43.0 to 5.44.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.44.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:07:11 +01:00
ddee1c9a8c website: Fix installations link in footer (#4053)
/index 404s this fixes it

Signed-off-by: GrahamSH <grahamshllk@gmail.com>

Signed-off-by: GrahamSH <grahamshllk@gmail.com>
2022-11-22 10:06:56 +01:00
84678c41a8 web: bump chartjs-adapter-moment from 1.0.0 to 1.0.1 in /web (#4057)
Bumps [chartjs-adapter-moment](https://github.com/chartjs/chartjs-adapter-moment) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/chartjs/chartjs-adapter-moment/releases)
- [Commits](https://github.com/chartjs/chartjs-adapter-moment/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: chartjs-adapter-moment
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:05:23 +01:00
7e1059dd43 web: bump @typescript-eslint/eslint-plugin from 5.43.0 to 5.44.0 in /web (#4055)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.43.0 to 5.44.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.44.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:05:14 +01:00
bc56ea6822 web: bump @sentry/tracing from 7.20.0 to 7.20.1 in /web (#4054)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.20.0 to 7.20.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/7.20.0...7.20.1)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:05:03 +01:00
768dc55a71 core: bump goauthentik.io/api/v3 from 3.2022101.8 to 3.2022110.1 (#4060)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022101.8 to 3.2022110.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022101.8...v3.2022110.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:02:25 +01:00
a0719ca65e root: fix build on arm64
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-21 22:38:25 +01:00
38c8555f36 web: bump API Client version (#4052)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-11-21 22:37:39 +01:00
5b8223808e Merge branch 'version-2022.11' 2022-11-21 22:14:33 +01:00
14f341f504 web/admin: fix error when importing duo devices
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-21 21:36:10 +01:00
c30aa90888 web: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-11-21 20:54:02 +01:00
89 changed files with 1253 additions and 406 deletions

View File

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

View File

@ -20,7 +20,12 @@
"todo-tree.tree.showCountsInTree": true, "todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true, "todo-tree.tree.showBadges": true,
"python.formatting.provider": "black", "python.formatting.provider": "black",
"yaml.customTags": ["!Find sequence", "!KeyOf scalar"], "yaml.customTags": [
"!Find sequence",
"!KeyOf scalar",
"!Context scalar",
"!Format sequence"
],
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index", "typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./web/node_modules/typescript/lib", "typescript.tsdk": "./web/node_modules/typescript/lib",

View File

@ -17,24 +17,24 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the - Focusing on what is best not just for us as individuals, but for the
overall community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or - The use of sexualized language or imagery, and sexual attention or
advances of any kind advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within

View File

@ -11,19 +11,22 @@ The following is a set of guidelines for contributing to authentik and its compo
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question) [I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[What should I know before I get started?](#what-should-i-know-before-i-get-started) [What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [The components](#the-components)
* [authentik's structure](#authentiks-structure) - [The components](#the-components)
- [authentik's structure](#authentiks-structure)
[How Can I Contribute?](#how-can-i-contribute) [How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements) - [Reporting Bugs](#reporting-bugs)
* [Your First Code Contribution](#your-first-code-contribution) - [Suggesting Enhancements](#suggesting-enhancements)
* [Pull Requests](#pull-requests) - [Your First Code Contribution](#your-first-code-contribution)
- [Pull Requests](#pull-requests)
[Styleguides](#styleguides) [Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [Python Styleguide](#python-styleguide) - [Git Commit Messages](#git-commit-messages)
* [Documentation Styleguide](#documentation-styleguide) - [Python Styleguide](#python-styleguide)
- [Documentation Styleguide](#documentation-styleguide)
## Code of Conduct ## Code of Conduct
@ -39,11 +42,11 @@ Either [create a question on GitHub](https://github.com/goauthentik/authentik/is
authentik consists of a few larger components: authentik consists of a few larger components:
- *authentik* the actual application server, is described below. - _authentik_ the actual application server, is described below.
- *outpost-proxy* is a Go application based on a forked version of oauth2_proxy, which does identity-aware reverse proxying. - _outpost-proxy_ is a Go application based on a forked version of oauth2_proxy, which does identity-aware reverse proxying.
- *outpost-ldap* is a Go LDAP server that uses the *authentik* application server as its backend - _outpost-ldap_ is a Go LDAP server that uses the _authentik_ application server as its backend
- *web* is the web frontend, both for administrating and using authentik. It is written in TypeScript using lit-html and the PatternFly CSS Library. - _web_ is the web frontend, both for administrating and using authentik. It is written in TypeScript using lit-html and the PatternFly CSS Library.
- *website* is the Website/documentation, which uses docusaurus. - _website_ is the Website/documentation, which uses docusaurus.
### authentik's structure ### authentik's structure
@ -137,10 +140,10 @@ This is documented in the [developer docs](https://goauthentik.io/developer-docs
The process described here has several goals: The process described here has several goals:
- Maintain authentik's quality - Maintain authentik's quality
- Fix problems that are important to users - Fix problems that are important to users
- Engage the community in working toward the best possible authentik - Engage the community in working toward the best possible authentik
- Enable a sustainable system for authentik's maintainers to review contributions - Enable a sustainable system for authentik's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers: Please follow these steps to have your contribution considered by the maintainers:
@ -154,10 +157,10 @@ While the prerequisites above must be satisfied prior to having your pull reques
### Git Commit Messages ### Git Commit Messages
* Use the format of `<package>: <verb> <description>` - Use the format of `<package>: <verb> <description>`
- See [here](#authentik-packages) for `package` - See [here](#authentik-packages) for `package`
- Example: `providers/saml2: fix parsing of requests` - Example: `providers/saml2: fix parsing of requests`
* Reference issues and pull requests liberally after the first line - Reference issues and pull requests liberally after the first line
### Python Styleguide ### Python Styleguide
@ -165,11 +168,11 @@ All Python code is linted with [black](https://black.readthedocs.io/en/stable/),
authentik runs on Python 3.9 at the time of writing this. authentik runs on Python 3.9 at the time of writing this.
* Use native type-annotations wherever possible. - Use native type-annotations wherever possible.
* Add meaningful docstrings when possible. - Add meaningful docstrings when possible.
* Ensure any database migrations work properly from the last stable version (this is checked via CI) - Ensure any database migrations work properly from the last stable version (this is checked via CI)
* If your code changes central functions, make sure nothing else is broken. - If your code changes central functions, make sure nothing else is broken.
### Documentation Styleguide ### Documentation Styleguide
* Use [MDX](https://mdxjs.com/) whenever appropriate. - Use [MDX](https://mdxjs.com/) whenever appropriate.

View File

@ -3,6 +3,7 @@ FROM --platform=${BUILDPLATFORM} docker.io/node:18 as website-builder
COPY ./website /work/website/ COPY ./website /work/website/
COPY ./blueprints /work/blueprints/ COPY ./blueprints /work/blueprints/
COPY ./SECURITY.md /work/
ENV NODE_ENV=production ENV NODE_ENV=production
WORKDIR /work/website WORKDIR /work/website
@ -62,7 +63,7 @@ COPY --from=poetry-locker /work/requirements-dev.txt /
RUN apt-get update && \ RUN apt-get update && \
# Required for installing pip packages # Required for installing pip packages
apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev && \ apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev && \
# Required for runtime # Required for runtime
apt-get install -y --no-install-recommends libxmlsec1-openssl libmaxminddb0 && \ apt-get install -y --no-install-recommends libxmlsec1-openssl libmaxminddb0 && \
# Required for bootstrap & healtcheck # Required for bootstrap & healtcheck
@ -80,6 +81,7 @@ RUN apt-get update && \
COPY ./authentik/ /authentik COPY ./authentik/ /authentik
COPY ./pyproject.toml / COPY ./pyproject.toml /
COPY ./xml /xml COPY ./xml /xml
COPY ./locale /locale
COPY ./tests /tests COPY ./tests /tests
COPY ./manage.py / COPY ./manage.py /
COPY ./blueprints /blueprints COPY ./blueprints /blueprints

View File

@ -25,10 +25,10 @@ For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/h
## Screenshots ## Screenshots
Light | Dark | Light | Dark |
--- | --- | ------------------------------------------------------ | ----------------------------------------------------- |
![](https://goauthentik.io/img/screen_apps_light.jpg) | ![](https://goauthentik.io/img/screen_apps_dark.jpg) | ![](https://goauthentik.io/img/screen_apps_light.jpg) | ![](https://goauthentik.io/img/screen_apps_dark.jpg) |
![](https://goauthentik.io/img/screen_admin_light.jpg) | ![](https://goauthentik.io/img/screen_admin_dark.jpg) | ![](https://goauthentik.io/img/screen_admin_light.jpg) | ![](https://goauthentik.io/img/screen_admin_dark.jpg) |
## Development ## Development

View File

@ -1,17 +1,43 @@
# Security Policy Authentik takes security very seriously. We follow the rules of [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure), and we urge our community to do so as well, instead of reporting vulnerabilities publicly. This allows us to patch the issue quickly, announce it's existence and release the fixed version.
## Supported Versions ## Supported Versions
(.x being the latest patch release for each version) (.x being the latest patch release for each version)
| Version | Supported | | Version | Supported |
| ---------- | ------------------ | | --------- | ------------------ |
| 2022.9.x | :white_check_mark: | | 2022.10.x | :white_check_mark: |
| 2022.10.x | :white_check_mark: | | 2022.11.x | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability
To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io) To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io). Be sure to include relevant information like which version you've found the issue in, instructions on how to reproduce the issue, and anything else that might make it easier for us to find the bug.
## Criticality levels
### High
- Authorization bypass
- Circumvention of policies
### Moderate
- Denial-of-Service attacks
### Low
- Unvalidated redirects
- Issues requiring uncommon setups
## Disclosure process
1. Issue is reported via Email as listed above.
2. The authentik Security team will try to reproduce the issue and ask for more information if required.
3. A criticality level is assigned.
4. A fix is created, and if possible tested by the issue reporter.
5. The fix is backported to other supported versions, and if possible a workaround for other versions is created.
6. An announcement is sent out with a fixed release date and criticality level of the issue. The announcement will be sent at least 24 hours before the release of the fix
7. The fixed version is released for the supported versions.
## Getting security notifications ## Getting security notifications

View File

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

View File

@ -53,6 +53,15 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created"
],
"default": "present"
},
"attrs": { "attrs": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -7,7 +7,6 @@ from django.apps import apps
from authentik.blueprints.apps import ManagedAppConfig from authentik.blueprints.apps import ManagedAppConfig
from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.models import BlueprintInstance
from authentik.lib.config import CONFIG
def apply_blueprint(*files: str): def apply_blueprint(*files: str):
@ -46,3 +45,13 @@ def reconcile_app(app_name: str):
return wrapper return wrapper
return wrapper_outer return wrapper_outer
def load_yaml_fixture(path: str, **kwargs) -> str:
"""Load yaml fixture, optionally formatting it with kwargs"""
with open(Path(__file__).resolve().parent / Path(path), "r", encoding="utf-8") as _fixture:
fixture = _fixture.read()
try:
return fixture % kwargs
except TypeError:
return fixture

View File

@ -0,0 +1,7 @@
version: 1
entries:
- identifiers:
name: "%(id)s"
slug: "%(id)s"
model: authentik_flows.flow
state: absent

View File

@ -0,0 +1,10 @@
version: 1
entries:
- identifiers:
name: "%(id)s"
slug: "%(id)s"
model: authentik_flows.flow
state: created
attrs:
designation: stage_configuration
title: foo

View File

@ -0,0 +1,10 @@
version: 1
entries:
- identifiers:
name: "%(id)s"
slug: "%(id)s"
model: authentik_flows.flow
state: present
attrs:
designation: stage_configuration
title: foo

View File

@ -0,0 +1,12 @@
version: 1
entries:
- identifiers:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt
attrs:
field_key: username
label: Username
type: username
required: true
placeholder: Username
order: 0

View File

@ -0,0 +1,10 @@
version: 1
context:
foo: bar
entries:
- attrs:
expression: return True
identifiers:
name: !Format [foo-%s-%s, !Context foo, !Context bar]
id: default-source-enrollment-if-username
model: authentik_policies_expression.expressionpolicy

View File

@ -1,6 +1,7 @@
"""Test blueprints v1""" """Test blueprints v1"""
from django.test import TransactionTestCase from django.test import TransactionTestCase
from authentik.blueprints.tests import load_yaml_fixture
from authentik.blueprints.v1.exporter import FlowExporter from authentik.blueprints.v1.exporter import FlowExporter
from authentik.blueprints.v1.importer import Importer, transaction_rollback from authentik.blueprints.v1.importer import Importer, transaction_rollback
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -10,32 +11,6 @@ from authentik.policies.models import PolicyBinding
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
from authentik.stages.user_login.models import UserLoginStage from authentik.stages.user_login.models import UserLoginStage
STATIC_PROMPT_EXPORT = """version: 1
entries:
- identifiers:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt
attrs:
field_key: username
label: Username
type: username
required: true
placeholder: Username
order: 0
"""
YAML_TAG_TESTS = """version: 1
context:
foo: bar
entries:
- attrs:
expression: return True
identifiers:
name: !Format [foo-%s-%s, !Context foo, !Context bar]
id: default-source-enrollment-if-username
model: authentik_policies_expression.expressionpolicy
"""
class TestBlueprintsV1(TransactionTestCase): class TestBlueprintsV1(TransactionTestCase):
"""Test Blueprints""" """Test Blueprints"""
@ -85,14 +60,14 @@ class TestBlueprintsV1(TransactionTestCase):
"""Test export and import it twice""" """Test export and import it twice"""
count_initial = Prompt.objects.filter(field_key="username").count() count_initial = Prompt.objects.filter(field_key="username").count()
importer = Importer(STATIC_PROMPT_EXPORT) importer = Importer(load_yaml_fixture("fixtures/static_prompt_export.yaml"))
self.assertTrue(importer.validate()[0]) self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply()) self.assertTrue(importer.apply())
count_before = Prompt.objects.filter(field_key="username").count() count_before = Prompt.objects.filter(field_key="username").count()
self.assertEqual(count_initial + 1, count_before) self.assertEqual(count_initial + 1, count_before)
importer = Importer(STATIC_PROMPT_EXPORT) importer = Importer(load_yaml_fixture("fixtures/static_prompt_export.yaml"))
self.assertTrue(importer.apply()) self.assertTrue(importer.apply())
self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before) self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
@ -100,7 +75,7 @@ class TestBlueprintsV1(TransactionTestCase):
def test_import_yaml_tags(self): def test_import_yaml_tags(self):
"""Test some yaml tags""" """Test some yaml tags"""
ExpressionPolicy.objects.filter(name="foo-foo-bar").delete() ExpressionPolicy.objects.filter(name="foo-foo-bar").delete()
importer = Importer(YAML_TAG_TESTS, {"bar": "baz"}) importer = Importer(load_yaml_fixture("fixtures/tags.yaml"), {"bar": "baz"})
self.assertTrue(importer.validate()[0]) self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply()) self.assertTrue(importer.apply())
self.assertTrue(ExpressionPolicy.objects.filter(name="foo-foo-bar")) self.assertTrue(ExpressionPolicy.objects.filter(name="foo-foo-bar"))

View File

@ -0,0 +1,82 @@
"""Test blueprints v1"""
from django.test import TransactionTestCase
from authentik.blueprints.tests import load_yaml_fixture
from authentik.blueprints.v1.importer import Importer
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
class TestBlueprintsV1State(TransactionTestCase):
"""Test Blueprints state attribute"""
def test_state_present(self):
"""Test state present"""
flow_slug = generate_id()
import_yaml = load_yaml_fixture("fixtures/state_present.yaml", id=flow_slug)
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
# Ensure object exists
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertEqual(flow.slug, flow_slug)
# Update object
flow.title = "bar"
flow.save()
flow.refresh_from_db()
self.assertEqual(flow.title, "bar")
# Ensure importer updates it
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertEqual(flow.title, "foo")
def test_state_created(self):
"""Test state created"""
flow_slug = generate_id()
import_yaml = load_yaml_fixture("fixtures/state_created.yaml", id=flow_slug)
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
# Ensure object exists
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertEqual(flow.slug, flow_slug)
# Update object
flow.title = "bar"
flow.save()
flow.refresh_from_db()
self.assertEqual(flow.title, "bar")
# Ensure importer doesn't update it
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertEqual(flow.title, "bar")
def test_state_absent(self):
"""Test state absent"""
flow_slug = generate_id()
import_yaml = load_yaml_fixture("fixtures/state_created.yaml", id=flow_slug)
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
# Ensure object exists
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertEqual(flow.slug, flow_slug)
import_yaml = load_yaml_fixture("fixtures/state_absent.yaml", id=flow_slug)
importer = Importer(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
flow: Flow = Flow.objects.filter(slug=flow_slug).first()
self.assertIsNone(flow)

View File

@ -41,11 +41,20 @@ class BlueprintEntryState:
instance: Optional[Model] = None instance: Optional[Model] = None
class BlueprintEntryDesiredState(Enum):
"""State an entry should be reconciled to"""
ABSENT = "absent"
PRESENT = "present"
CREATED = "created"
@dataclass @dataclass
class BlueprintEntry: class BlueprintEntry:
"""Single entry of a blueprint""" """Single entry of a blueprint"""
model: str model: str
state: BlueprintEntryDesiredState = field(default=BlueprintEntryDesiredState.PRESENT)
identifiers: dict[str, Any] = field(default_factory=dict) identifiers: dict[str, Any] = field(default_factory=dict)
attrs: Optional[dict[str, Any]] = field(default_factory=dict) attrs: Optional[dict[str, Any]] = field(default_factory=dict)
@ -227,8 +236,15 @@ class BlueprintDumper(SafeDumper):
self.add_representer(UUID, lambda self, data: self.represent_str(str(data))) self.add_representer(UUID, lambda self, data: self.represent_str(str(data)))
self.add_representer(OrderedDict, lambda self, data: self.represent_dict(dict(data))) self.add_representer(OrderedDict, lambda self, data: self.represent_dict(dict(data)))
self.add_representer(Enum, lambda self, data: self.represent_str(data.value)) self.add_representer(Enum, lambda self, data: self.represent_str(data.value))
self.add_representer(
BlueprintEntryDesiredState, lambda self, data: self.represent_str(data.value)
)
self.add_representer(None, lambda self, data: self.represent_str(str(data))) self.add_representer(None, lambda self, data: self.represent_str(str(data)))
def ignore_aliases(self, data):
"""Don't use any YAML anchors"""
return True
def represent(self, data) -> None: def represent(self, data) -> None:
if is_dataclass(data): if is_dataclass(data):

View File

@ -3,6 +3,7 @@ from contextlib import contextmanager
from copy import deepcopy from copy import deepcopy
from typing import Any, Optional from typing import Any, Optional
from dacite.config import Config
from dacite.core import from_dict from dacite.core import from_dict
from dacite.exceptions import DaciteError from dacite.exceptions import DaciteError
from deepmerge import always_merger from deepmerge import always_merger
@ -20,6 +21,7 @@ from yaml import load
from authentik.blueprints.v1.common import ( from authentik.blueprints.v1.common import (
Blueprint, Blueprint,
BlueprintEntry, BlueprintEntry,
BlueprintEntryDesiredState,
BlueprintEntryState, BlueprintEntryState,
BlueprintLoader, BlueprintLoader,
EntryInvalidError, EntryInvalidError,
@ -82,7 +84,9 @@ class Importer:
self.logger = get_logger() self.logger = get_logger()
import_dict = load(yaml_input, BlueprintLoader) import_dict = load(yaml_input, BlueprintLoader)
try: try:
self.__import = from_dict(Blueprint, import_dict) self.__import = from_dict(
Blueprint, import_dict, config=Config(cast=[BlueprintEntryDesiredState])
)
except DaciteError as exc: except DaciteError as exc:
raise EntryInvalidError from exc raise EntryInvalidError from exc
ctx = {} ctx = {}
@ -135,7 +139,7 @@ class Importer:
sub_query &= Q(**{identifier: value}) sub_query &= Q(**{identifier: value})
return main_query | sub_query return main_query | sub_query
def _validate_single(self, entry: BlueprintEntry) -> BaseSerializer: def _validate_single(self, entry: BlueprintEntry) -> Optional[BaseSerializer]:
"""Validate a single entry""" """Validate a single entry"""
model_app_label, model_name = entry.model.split(".") model_app_label, model_name = entry.model.split(".")
model: type[SerializerModel] = registry.get_model(model_app_label, model_name) model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
@ -168,8 +172,11 @@ class Importer:
existing_models = model.objects.filter(self.__query_from_identifier(updated_identifiers)) existing_models = model.objects.filter(self.__query_from_identifier(updated_identifiers))
serializer_kwargs = {} serializer_kwargs = {}
if not isinstance(model(), BaseMetaModel) and existing_models.exists(): model_instance = existing_models.first()
model_instance = existing_models.first() if not isinstance(model(), BaseMetaModel) and model_instance:
if entry.state == BlueprintEntryDesiredState.CREATED:
self.logger.debug("instance exists, skipping")
return None
self.logger.debug( self.logger.debug(
"initialise serializer with instance", "initialise serializer with instance",
model=model, model=model,
@ -234,12 +241,25 @@ class Importer:
except EntryInvalidError as exc: except EntryInvalidError as exc:
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
return False return False
if not serializer:
continue
model = serializer.save() if entry.state in [
if "pk" in entry.identifiers: BlueprintEntryDesiredState.PRESENT,
self.__pk_map[entry.identifiers["pk"]] = model.pk BlueprintEntryDesiredState.CREATED,
entry._state = BlueprintEntryState(model) ]:
self.logger.debug("updated model", model=model) model = serializer.save()
if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = model.pk
entry._state = BlueprintEntryState(model)
self.logger.debug("updated model", model=model)
elif entry.state == BlueprintEntryDesiredState.ABSENT:
instance: Optional[Model] = serializer.instance
if instance:
instance.delete()
self.logger.debug("deleted model", mode=instance)
continue
self.logger.debug("entry to delete with no instance, skipping")
return True return True
def validate(self) -> tuple[bool, list[EventDict]]: def validate(self) -> tuple[bool, list[EventDict]]:

View File

@ -1,6 +1,8 @@
"""authentik shell command""" """authentik shell command"""
import code import code
import platform import platform
import sys
import traceback
from django.apps import apps from django.apps import apps
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -89,6 +91,21 @@ class Command(BaseCommand):
exec(options["command"], namespace) # nosec # noqa exec(options["command"], namespace) # nosec # noqa
return return
try:
hook = sys.__interactivehook__
except AttributeError:
# Match the behavior of the cpython shell where a missing
# sys.__interactivehook__ is ignored.
pass
else:
try:
hook()
except Exception: # pylint: disable=broad-except
# Match the behavior of the cpython shell where an error in
# sys.__interactivehook__ prints a warning and the exception
# and continues.
print("Failed calling sys.__interactivehook__")
traceback.print_exc()
# Try to enable tab-complete # Try to enable tab-complete
try: try:
import readline import readline

View File

@ -71,6 +71,7 @@ class FlowSerializer(ModelSerializer):
"export_url", "export_url",
"layout", "layout",
"denied_action", "denied_action",
"authentication",
] ]
extra_kwargs = { extra_kwargs = {
"background": {"read_only": True}, "background": {"read_only": True},

View File

@ -1,4 +1,6 @@
"""flow exceptions""" """flow exceptions"""
from typing import Optional
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
@ -6,15 +8,15 @@ from authentik.policies.types import PolicyResult
class FlowNonApplicableException(SentryIgnoredException): class FlowNonApplicableException(SentryIgnoredException):
"""Flow does not apply to current user (denied by policy).""" """Flow does not apply to current user (denied by policy, or otherwise)."""
policy_result: PolicyResult policy_result: Optional[PolicyResult] = None
@property @property
def messages(self) -> str: def messages(self) -> str:
"""Get messages from policy result, fallback to generic reason""" """Get messages from policy result, fallback to generic reason"""
if len(self.policy_result.messages) < 1: if not self.policy_result or len(self.policy_result.messages) < 1:
return _("Flow does not apply to current user (denied by policy).") return _("Flow does not apply to current user.")
return "\n".join(self.policy_result.messages) return "\n".join(self.policy_result.messages)

View File

@ -0,0 +1,27 @@
# Generated by Django 4.1.3 on 2022-11-30 09:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0023_flow_denied_action"),
]
operations = [
migrations.AddField(
model_name="flow",
name="authentication",
field=models.TextField(
choices=[
("none", "None"),
("require_authenticated", "Require Authenticated"),
("require_unauthenticated", "Require Unauthenticated"),
("require_superuser", "Require Superuser"),
],
default="none",
help_text="Required level of authentication and authorization to access a flow.",
),
),
]

View File

@ -23,6 +23,15 @@ if TYPE_CHECKING:
LOGGER = get_logger() LOGGER = get_logger()
class FlowAuthenticationRequirement(models.TextChoices):
"""Required level of authentication and authorization to access a flow"""
NONE = "none"
REQUIRE_AUTHENTICATED = "require_authenticated"
REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
REQUIRE_SUPERUSER = "require_superuser"
class NotConfiguredAction(models.TextChoices): class NotConfiguredAction(models.TextChoices):
"""Decides how the FlowExecutor should proceed when a stage isn't configured""" """Decides how the FlowExecutor should proceed when a stage isn't configured"""
@ -152,6 +161,12 @@ class Flow(SerializerModel, PolicyBindingModel):
help_text=_("Configure what should happen when a flow denies access to a user."), help_text=_("Configure what should happen when a flow denies access to a user."),
) )
authentication = models.TextField(
choices=FlowAuthenticationRequirement.choices,
default=FlowAuthenticationRequirement.NONE,
help_text=_("Required level of authentication and authorization to access a flow."),
)
@property @property
def background_url(self) -> str: def background_url(self) -> str:
"""Get the URL to the background image. If the name is /static or starts with http """Get the URL to the background image. If the name is /static or starts with http

View File

@ -13,7 +13,14 @@ from authentik.events.models import cleanse_dict
from authentik.flows.apps import HIST_FLOWS_PLAN_TIME from authentik.flows.apps import HIST_FLOWS_PLAN_TIME
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage, in_memory_stage from authentik.flows.models import (
Flow,
FlowAuthenticationRequirement,
FlowDesignation,
FlowStageBinding,
Stage,
in_memory_stage,
)
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
@ -117,11 +124,30 @@ class FlowPlanner:
self.flow = flow self.flow = flow
self._logger = get_logger().bind(flow_slug=flow.slug) self._logger = get_logger().bind(flow_slug=flow.slug)
def _check_authentication(self, request: HttpRequest):
"""Check the flow's authentication level is matched by `request`"""
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
and not request.user.is_authenticated
):
raise FlowNonApplicableException()
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_UNAUTHENTICATED
and request.user.is_authenticated
):
raise FlowNonApplicableException()
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_SUPERUSER
and not request.user.is_superuser
):
raise FlowNonApplicableException()
def plan( def plan(
self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None
) -> FlowPlan: ) -> FlowPlan:
"""Check each of the flows' policies, check policies for each stage with PolicyBinding """Check each of the flows' policies, check policies for each stage with PolicyBinding
and return ordered list""" and return ordered list"""
self._check_authentication(request)
with Hub.current.start_span( with Hub.current.start_span(
op="authentik.flow.planner.plan", description=self.flow.slug op="authentik.flow.planner.plan", description=self.flow.slug
) as span: ) as span:

View File

@ -1,6 +1,7 @@
"""flow planner tests""" """flow planner tests"""
from unittest.mock import MagicMock, Mock, PropertyMock, patch from unittest.mock import MagicMock, Mock, PropertyMock, patch
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import cache from django.core.cache import cache
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
@ -8,10 +9,10 @@ from django.urls import reverse
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from authentik.core.models import User from authentik.core.models import User
from authentik.core.tests.utils import create_test_flow from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.lib.tests.utils import dummy_get_response from authentik.lib.tests.utils import dummy_get_response
from authentik.policies.dummy.models import DummyPolicy from authentik.policies.dummy.models import DummyPolicy
@ -43,6 +44,30 @@ class TestFlowPlanner(TestCase):
planner = FlowPlanner(flow) planner = FlowPlanner(flow)
planner.plan(request) planner.plan(request)
def test_authentication(self):
"""Test flow authentication"""
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
planner.plan(request)
with self.assertRaises(FlowNonApplicableException):
flow.authentication = FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
FlowPlanner(flow).plan(request)
with self.assertRaises(FlowNonApplicableException):
flow.authentication = FlowAuthenticationRequirement.REQUIRE_SUPERUSER
FlowPlanner(flow).plan(request)
request.user = create_test_admin_user()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
planner.plan(request)
@patch( @patch(
"authentik.policies.engine.PolicyEngine.result", "authentik.policies.engine.PolicyEngine.result",
POLICY_RETURN_FALSE, POLICY_RETURN_FALSE,

View File

@ -202,10 +202,10 @@ class ResponseProcessor:
"""Get all attributes sent""" """Get all attributes sent"""
attributes = {} attributes = {}
assertion = self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion") assertion = self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
if not assertion: if assertion is None:
raise ValueError("Assertion element not found") raise ValueError("Assertion element not found")
attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement") attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
if not attribute_statement: if attribute_statement is None:
raise ValueError("Attribute statement element not found") raise ValueError("Attribute statement element not found")
# Get all attributes and their values into a dict # Get all attributes and their values into a dict
for attribute in attribute_statement.iterchildren(): for attribute in attribute_statement.iterchildren():

View File

@ -118,12 +118,12 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
.first() .first()
) )
if not user: if not user:
return Response(data={"non_field_errors": ["user does not exist"]}, status=400) return Response(data={"non_field_errors": ["User does not exist."]}, status=400)
device = DuoDevice.objects.filter( device = DuoDevice.objects.filter(
duo_user_id=request.data.get("duo_user_id"), user=user, stage=stage duo_user_id=request.data.get("duo_user_id"), user=user, stage=stage
).first() ).first()
if device: if device:
return Response(data={"non_field_errors": ["device exists already"]}, status=400) return Response(data={"non_field_errors": ["Device exists already."]}, status=400)
DuoDevice.objects.create( DuoDevice.objects.create(
duo_user_id=request.data.get("duo_user_id"), duo_user_id=request.data.get("duo_user_id"),
user=user, user=user,

View File

@ -1,7 +1,6 @@
"""Test validator stage""" """Test validator stage"""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from hashlib import sha256 from hashlib import sha256
from http.cookies import SimpleCookie
from time import sleep from time import sleep
from django.conf import settings from django.conf import settings
@ -76,7 +75,7 @@ class AuthenticatorValidateStageTOTPTests(FlowTestCase):
component="ak-stage-authenticator-validate", component="ak-stage-authenticator-validate",
) )
def test_last_auth_threshold_valid(self) -> SimpleCookie: def test_last_auth_threshold_valid(self):
"""Test last_auth_threshold""" """Test last_auth_threshold"""
ident_stage = IdentificationStage.objects.create( ident_stage = IdentificationStage.objects.create(
name=generate_id(), name=generate_id(),
@ -115,12 +114,47 @@ class AuthenticatorValidateStageTOTPTests(FlowTestCase):
) )
self.assertIn(COOKIE_NAME_MFA, response.cookies) self.assertIn(COOKIE_NAME_MFA, response.cookies)
self.assertStageResponse(response, component="xak-flow-redirect", to="/") self.assertStageResponse(response, component="xak-flow-redirect", to="/")
return response.cookies
def test_last_auth_skip(self): def test_last_auth_skip(self):
"""Test valid cookie""" """Test valid cookie"""
cookies = self.test_last_auth_threshold_valid() ident_stage = IdentificationStage.objects.create(
mfa_cookie = cookies[COOKIE_NAME_MFA] name=generate_id(),
user_fields=[
UserFields.USERNAME,
],
)
device: TOTPDevice = TOTPDevice.objects.create(
user=self.user,
confirmed=True,
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
last_auth_threshold="hours=1",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.TOTP],
)
stage.configuration_stages.set([ident_stage])
FlowStageBinding.objects.create(target=self.flow, stage=ident_stage, order=0)
FlowStageBinding.objects.create(target=self.flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
# Verify token once here to set last_t etc
totp = TOTP(device.bin_key)
sleep(1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
{"code": str(totp.token())},
)
self.assertIn(COOKIE_NAME_MFA, response.cookies)
self.assertStageResponse(response, component="xak-flow-redirect", to="/")
mfa_cookie = response.cookies[COOKIE_NAME_MFA]
self.client.logout() self.client.logout()
self.client.cookies[COOKIE_NAME_MFA] = mfa_cookie self.client.cookies[COOKIE_NAME_MFA] = mfa_cookie
response = self.client.post( response = self.client.post(

View File

@ -44,6 +44,28 @@ class TestEmailStage(FlowTestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
)
def test_rendering_locale(self):
"""Test with pending user"""
self.user.attributes = {"settings": {"locale": "de"}}
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertNotIn(
"You recently requested to change your password", mail.outbox[0].alternatives[0][0]
)
def test_without_user(self): def test_without_user(self):
"""Test without pending user""" """Test without pending user"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
@ -55,6 +77,10 @@ class TestEmailStage(FlowTestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
)
def test_pending_user(self): def test_pending_user(self):
"""Test with pending user""" """Test with pending user"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
@ -64,16 +90,16 @@ class TestEmailStage(FlowTestCase):
session.save() session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
with patch( response = self.client.post(url)
"authentik.stages.email.models.EmailStage.backend_class", self.assertEqual(response.status_code, 200)
PropertyMock(return_value=EmailBackend), self.assertEqual(len(mail.outbox), 1)
): self.assertEqual(mail.outbox[0].subject, "authentik")
response = self.client.post(url) self.assertEqual(mail.outbox[0].to, [self.user.email])
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, [self.user.email])
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
)
def test_pending_user_override(self): def test_pending_user_override(self):
"""Test with pending user (override to)""" """Test with pending user (override to)"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
@ -84,25 +110,21 @@ class TestEmailStage(FlowTestCase):
session.save() session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
with patch( response = self.client.post(url)
"authentik.stages.email.models.EmailStage.backend_class", self.assertEqual(response.status_code, 200)
PropertyMock(return_value=EmailBackend), self.assertEqual(len(mail.outbox), 1)
): self.assertEqual(mail.outbox[0].subject, "authentik")
response = self.client.post(url) self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
@patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=SMTPEmailBackend),
)
def test_use_global_settings(self): def test_use_global_settings(self):
"""Test use_global_settings""" """Test use_global_settings"""
host = "some-unique-string" host = "some-unique-string"
with patch( with self.settings(EMAIL_HOST=host):
"authentik.stages.email.models.EmailStage.backend_class", self.assertEqual(EmailStage(use_global_settings=True).backend.host, host)
PropertyMock(return_value=SMTPEmailBackend),
):
with self.settings(EMAIL_HOST=host):
self.assertEqual(EmailStage(use_global_settings=True).backend.host, host)
def test_token(self): def test_token(self):
"""Test with token""" """Test with token"""

View File

@ -8,6 +8,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict from authentik.core.api.utils import is_dict
from authentik.flows.api.flows import FlowSerializer
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
@ -49,6 +50,7 @@ class InvitationSerializer(ModelSerializer):
created_by = GroupMemberSerializer(read_only=True) created_by = GroupMemberSerializer(read_only=True)
fixed_data = JSONField(validators=[is_dict], required=False) fixed_data = JSONField(validators=[is_dict], required=False)
flow_obj = FlowSerializer(read_only=True, required=False, source="flow")
class Meta: class Meta:
@ -60,6 +62,8 @@ class InvitationSerializer(ModelSerializer):
"fixed_data", "fixed_data",
"created_by", "created_by",
"single_use", "single_use",
"flow",
"flow_obj",
] ]
@ -69,8 +73,8 @@ class InvitationViewSet(UsedByMixin, ModelViewSet):
queryset = Invitation.objects.all() queryset = Invitation.objects.all()
serializer_class = InvitationSerializer serializer_class = InvitationSerializer
ordering = ["-expires"] ordering = ["-expires"]
search_fields = ["name", "created_by__username", "expires"] search_fields = ["name", "created_by__username", "expires", "flow__slug"]
filterset_fields = ["name", "created_by__username", "expires"] filterset_fields = ["name", "created_by__username", "expires", "flow__slug"]
def perform_create(self, serializer: InvitationSerializer): def perform_create(self, serializer: InvitationSerializer):
serializer.save(created_by=self.request.user) serializer.save(created_by=self.request.user)

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.4 on 2022-12-20 13:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0024_flow_authentication"),
("authentik_stages_invitation", "0001_squashed_0006_invitation_name"),
]
operations = [
migrations.AddField(
model_name="invitation",
name="flow",
field=models.ForeignKey(
default=None,
help_text="When set, only the configured flow can use this invitation.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_flows.flow",
),
),
]

View File

@ -55,6 +55,13 @@ class Invitation(SerializerModel, ExpiringModel):
name = models.SlugField() name = models.SlugField()
flow = models.ForeignKey(
"authentik_flows.Flow",
default=None,
null=True,
on_delete=models.SET_DEFAULT,
help_text=_("When set, only the configured flow can use this invitation."),
)
single_use = models.BooleanField( single_use = models.BooleanField(
default=False, default=False,
help_text=_("When enabled, the invitation will be deleted after usage."), help_text=_("When enabled, the invitation will be deleted after usage."),

View File

@ -35,22 +35,30 @@ class InvitationStageView(StageView):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY_CONTEXT] return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY_CONTEXT]
return None return None
def get_invite(self) -> Optional[Invitation]:
"""Check the token, find the invite and check it's flow"""
token = self.get_token()
if not token:
return None
invite: Invitation = Invitation.objects.filter(pk=token).first()
if not invite:
self.logger.debug("invalid invitation", token=token)
return None
if invite.flow and invite.flow.pk != self.executor.plan.flow_pk:
self.logger.debug("invite for incorrect flow", expected=invite.flow.slug)
return None
return invite
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Apply data to the current flow based on a URL""" """Apply data to the current flow based on a URL"""
stage: InvitationStage = self.executor.current_stage stage: InvitationStage = self.executor.current_stage
token = self.get_token()
if not token: invite = self.get_invite()
# No Invitation was given, raise error or continue if not invite:
if stage.continue_flow_without_invitation: if stage.continue_flow_without_invitation:
return self.executor.stage_ok() return self.executor.stage_ok()
return self.executor.stage_invalid() return self.executor.stage_invalid()
invite: Invitation = Invitation.objects.filter(pk=token).first()
if not invite:
self.logger.debug("invalid invitation", token=token)
if stage.continue_flow_without_invitation:
return self.executor.stage_ok()
return self.executor.stage_invalid()
self.executor.plan.context[INVITATION_IN_EFFECT] = True self.executor.plan.context[INVITATION_IN_EFFECT] = True
self.executor.plan.context[INVITATION] = invite self.executor.plan.context[INVITATION] = invite

View File

@ -23,7 +23,7 @@ from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
class TestUserLoginStage(FlowTestCase): class TestInvitationStage(FlowTestCase):
"""Login tests""" """Login tests"""
def setUp(self): def setUp(self):
@ -98,6 +98,33 @@ class TestUserLoginStage(FlowTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_invalid_flow(self):
"""Test with invitation, invalid flow limit"""
invalid_flow = create_test_flow(FlowDesignation.ENROLLMENT)
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
data = {"foo": "bar"}
invite = Invitation.objects.create(
created_by=get_anonymous_user(), fixed_data=data, flow=invalid_flow
)
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
base_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
args = urlencode({INVITATION_TOKEN_KEY: invite.pk.hex})
response = self.client.get(base_url + f"?query={args}")
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertStageResponse(
response,
flow=self.flow,
component="ak-stage-access-denied",
)
def test_with_invitation_prompt_data(self): def test_with_invitation_prompt_data(self):
"""Test with invitation, check data in session""" """Test with invitation, check data in session"""
data = {"foo": "bar"} data = {"foo": "bar"}

View File

@ -137,7 +137,7 @@ class TestPromptStage(FlowTestCase):
self.assertIn(prompt.label, response.content.decode()) self.assertIn(prompt.label, response.content.decode())
self.assertIn(prompt.placeholder, response.content.decode()) self.assertIn(prompt.placeholder, response.content.decode())
def test_valid_challenge_with_policy(self) -> PromptChallengeResponse: def test_valid_challenge_with_policy(self):
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
expr = ( expr = (
@ -151,9 +151,8 @@ class TestPromptStage(FlowTestCase):
None, stage=self.stage, plan=plan, data=self.prompt_data None, stage=self.stage, plan=plan, data=self.prompt_data
) )
self.assertEqual(challenge_response.is_valid(), True) self.assertEqual(challenge_response.is_valid(), True)
return challenge_response
def test_invalid_challenge(self) -> PromptChallengeResponse: def test_invalid_challenge(self):
"""Test challenge_response validation""" """Test challenge_response validation"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
expr = "False" expr = "False"
@ -164,7 +163,6 @@ class TestPromptStage(FlowTestCase):
None, stage=self.stage, plan=plan, data=self.prompt_data None, stage=self.stage, plan=plan, data=self.prompt_data
) )
self.assertEqual(challenge_response.is_valid(), False) self.assertEqual(challenge_response.is_valid(), False)
return challenge_response
def test_valid_challenge_request(self): def test_valid_challenge_request(self):
"""Test a request with valid challenge_response data""" """Test a request with valid challenge_response data"""
@ -173,7 +171,18 @@ class TestPromptStage(FlowTestCase):
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
challenge_response = self.test_valid_challenge_with_policy() plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
expr = (
"return request.context['prompt_data']['password_prompt'] "
"== request.context['prompt_data']['password2_prompt']"
)
expr_policy = ExpressionPolicy.objects.create(name="validate-form", expression=expr)
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
)
self.assertEqual(challenge_response.is_valid(), True)
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()): with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
response = self.client.post( response = self.client.post(

View File

@ -15,6 +15,7 @@ class UserWriteStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + [ fields = StageSerializer.Meta.fields + [
"create_users_as_inactive", "create_users_as_inactive",
"create_users_group", "create_users_group",
"can_create_users",
"user_path_template", "user_path_template",
] ]

View File

@ -0,0 +1,21 @@
# Generated by Django 4.1.4 on 2022-12-22 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_user_write", "0005_userwritestage_user_path_template"),
]
operations = [
migrations.AddField(
model_name="userwritestage",
name="can_create_users",
field=models.BooleanField(
default=True,
help_text="When set, this stage can create users. If not enabled and no user is available, stage will fail.",
),
),
]

View File

@ -13,6 +13,16 @@ class UserWriteStage(Stage):
"""Writes currently pending data into the pending user, or if no user exists, """Writes currently pending data into the pending user, or if no user exists,
creates a new user with the data.""" creates a new user with the data."""
can_create_users = models.BooleanField(
default=True,
help_text=_(
(
"When set, this stage can create users. "
"If not enabled and no user is available, stage will fail."
)
),
)
create_users_as_inactive = models.BooleanField( create_users_as_inactive = models.BooleanField(
default=False, default=False,
help_text=_("When set, newly created users are inactive and cannot login."), help_text=_("When set, newly created users are inactive and cannot login."),

View File

@ -1,10 +1,9 @@
"""Write stage logic""" """Write stage logic"""
from typing import Any from typing import Any, Optional
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.db import transaction from django.db import transaction
from django.db.utils import IntegrityError from django.db.utils import IntegrityError, InternalError
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -47,7 +46,7 @@ class UserWriteStageView(StageView):
"""Wrapper for post requests""" """Wrapper for post requests"""
return self.get(request) return self.get(request)
def ensure_user(self) -> tuple[User, bool]: def ensure_user(self) -> tuple[Optional[User], bool]:
"""Ensure a user exists""" """Ensure a user exists"""
user_created = False user_created = False
path = self.executor.plan.context.get( path = self.executor.plan.context.get(
@ -55,7 +54,11 @@ class UserWriteStageView(StageView):
) )
if path == "": if path == "":
path = User.default_path() path = User.default_path()
if not self.request.user.is_anonymous:
self.executor.plan.context.setdefault(PLAN_CONTEXT_PENDING_USER, self.request.user)
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
if not self.executor.current_stage.can_create_users:
return None, False
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
is_active=not self.executor.current_stage.create_users_as_inactive, is_active=not self.executor.current_stage.create_users_as_inactive,
path=path, path=path,
@ -110,11 +113,14 @@ class UserWriteStageView(StageView):
a new user is created.""" a new user is created."""
if PLAN_CONTEXT_PROMPT not in self.executor.plan.context: if PLAN_CONTEXT_PROMPT not in self.executor.plan.context:
message = _("No Pending data.") message = _("No Pending data.")
messages.error(request, message)
self.logger.debug(message) self.logger.debug(message)
return self.executor.stage_invalid() return self.executor.stage_invalid(message)
data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] data = self.executor.plan.context[PLAN_CONTEXT_PROMPT]
user, user_created = self.ensure_user() user, user_created = self.ensure_user()
if not user:
message = _("No user found and can't create new user.")
self.logger.info(message)
return self.executor.stage_invalid(message)
# Before we change anything, check if the user is the same as in the request # Before we change anything, check if the user is the same as in the request
# and we're updating a password. In that case we need to update the session hash # and we're updating a password. In that case we need to update the session hash
# Also check that we're not currently impersonating, so we don't update the session # Also check that we're not currently impersonating, so we don't update the session
@ -137,9 +143,9 @@ class UserWriteStageView(StageView):
user.ak_groups.add(self.executor.current_stage.create_users_group) user.ak_groups.add(self.executor.current_stage.create_users_group)
if PLAN_CONTEXT_GROUPS in self.executor.plan.context: if PLAN_CONTEXT_GROUPS in self.executor.plan.context:
user.ak_groups.add(*self.executor.plan.context[PLAN_CONTEXT_GROUPS]) user.ak_groups.add(*self.executor.plan.context[PLAN_CONTEXT_GROUPS])
except (IntegrityError, ValueError, TypeError) as exc: except (IntegrityError, ValueError, TypeError, InternalError) as exc:
self.logger.warning("Failed to save user", exc=exc) self.logger.warning("Failed to save user", exc=exc)
return self.executor.stage_invalid() return self.executor.stage_invalid(_("Failed to save user"))
user_write.send(sender=self, request=request, user=user, data=data, created=user_created) user_write.send(sender=self, request=request, user=user, data=data, created=user_created)
# Check if the password has been updated, and update the session auth hash # Check if the password has been updated, and update the session auth hash
if should_update_session: if should_update_session:

View File

@ -1,6 +1,4 @@
"""write tests""" """write tests"""
import string
from random import SystemRandom
from unittest.mock import patch from unittest.mock import patch
from django.urls import reverse from django.urls import reverse
@ -14,6 +12,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_key
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.models import UserWriteStage from authentik.stages.user_write.models import UserWriteStage
from authentik.stages.user_write.stage import PLAN_CONTEXT_GROUPS, UserWriteStageView from authentik.stages.user_write.stage import PLAN_CONTEXT_GROUPS, UserWriteStageView
@ -32,12 +31,11 @@ class TestUserWriteStage(FlowTestCase):
) )
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
self.source = Source.objects.create(name="fake_source") self.source = Source.objects.create(name="fake_source")
self.user = create_test_admin_user()
def test_user_create(self): def test_user_create(self):
"""Test creation of user""" """Test creation of user"""
password = "".join( password = generate_key()
SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(8)
)
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PROMPT] = { plan.context[PLAN_CONTEXT_PROMPT] = {
@ -66,9 +64,7 @@ class TestUserWriteStage(FlowTestCase):
def test_user_update(self): def test_user_update(self):
"""Test update of existing user""" """Test update of existing user"""
new_password = "".join( new_password = generate_key()
SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(8)
)
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create( plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create(
username="unittest", email="test@goauthentik.io" username="unittest", email="test@goauthentik.io"
@ -142,6 +138,49 @@ class TestUserWriteStage(FlowTestCase):
component="ak-stage-access-denied", component="ak-stage-access-denied",
) )
def test_authenticated_no_user(self):
"""Test user in session and none in plan"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
self.client.force_login(self.user)
session = self.client.session
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "foo",
"attribute_some-custom-attribute": "test",
"some_ignored_attribute": "bar",
}
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.user.refresh_from_db()
self.assertEqual(self.user.username, "foo")
def test_no_create(self):
"""Test can_create_users set to false"""
self.stage.can_create_users = False
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "foo",
"attribute_some-custom-attribute": "test",
"some_ignored_attribute": "bar",
}
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
)
@patch( @patch(
"authentik.flows.views.executor.to_stage_response", "authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK, TO_STAGE_RESPONSE_MOCK,

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: Change Password name: Change Password
title: Change password title: Change password
authentication: require_authenticated
identifiers: identifiers:
slug: default-password-change slug: default-password-change
model: authentik_flows.flow model: authentik_flows.flow
@ -44,6 +45,8 @@ entries:
name: default-password-change-write name: default-password-change-write
id: default-password-change-write id: default-password-change-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs:
can_create_users: false
- identifiers: - identifiers:
order: 0 order: 0
stage: !KeyOf default-password-change-prompt stage: !KeyOf default-password-change-prompt

View File

@ -11,6 +11,7 @@ entries:
designation: authentication designation: authentication
name: Welcome to authentik! name: Welcome to authentik!
title: Welcome to authentik! title: Welcome to authentik!
authentication: require_unauthenticated
identifiers: identifiers:
slug: default-authentication-flow slug: default-authentication-flow
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: invalidation designation: invalidation
name: Logout name: Logout
title: Default Invalidation Flow title: Default Invalidation Flow
authentication: require_authenticated
identifiers: identifiers:
slug: default-invalidation-flow slug: default-invalidation-flow
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: default-authenticator-static-setup name: default-authenticator-static-setup
title: Setup Static OTP Tokens title: Setup Static OTP Tokens
authentication: require_authenticated
identifiers: identifiers:
slug: default-authenticator-static-setup slug: default-authenticator-static-setup
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: default-authenticator-totp-setup name: default-authenticator-totp-setup
title: Setup Two-Factor authentication title: Setup Two-Factor authentication
authentication: require_authenticated
identifiers: identifiers:
slug: default-authenticator-totp-setup slug: default-authenticator-totp-setup
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: default-authenticator-webauthn-setup name: default-authenticator-webauthn-setup
title: Setup WebAuthn title: Setup WebAuthn
authentication: require_authenticated
identifiers: identifiers:
slug: default-authenticator-webauthn-setup slug: default-authenticator-webauthn-setup
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: authorization designation: authorization
name: Authorize Application name: Authorize Application
title: Redirecting to %(app)s title: Redirecting to %(app)s
authentication: require_authenticated
identifiers: identifiers:
slug: default-provider-authorization-explicit-consent slug: default-provider-authorization-explicit-consent
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: authorization designation: authorization
name: Authorize Application name: Authorize Application
title: Redirecting to %(app)s title: Redirecting to %(app)s
authentication: require_authenticated
identifiers: identifiers:
slug: default-provider-authorization-implicit-consent slug: default-provider-authorization-implicit-consent
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: authentication designation: authentication
name: Welcome to authentik! name: Welcome to authentik!
title: Welcome to authentik! title: Welcome to authentik!
authentication: require_unauthenticated
identifiers: identifiers:
slug: default-source-authentication slug: default-source-authentication
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: enrollment designation: enrollment
name: Welcome to authentik! Please select a username. name: Welcome to authentik! Please select a username.
title: Welcome to authentik! Please select a username. title: Welcome to authentik! Please select a username.
authentication: none
identifiers: identifiers:
slug: default-source-enrollment slug: default-source-enrollment
model: authentik_flows.flow model: authentik_flows.flow
@ -56,6 +57,8 @@ entries:
name: default-source-enrollment-write name: default-source-enrollment-write
id: default-source-enrollment-write id: default-source-enrollment-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs:
can_create_users: true
- attrs: - attrs:
re_evaluate_policies: true re_evaluate_policies: true
identifiers: identifiers:

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: Pre-Authentication name: Pre-Authentication
title: Pre-authentication title: Pre-authentication
authentication: none
identifiers: identifiers:
slug: default-source-pre-authentication slug: default-source-pre-authentication
model: authentik_flows.flow model: authentik_flows.flow

View File

@ -6,6 +6,7 @@ entries:
designation: stage_configuration designation: stage_configuration
name: User settings name: User settings
title: Update your info title: Update your info
authentication: require_authenticated
identifiers: identifiers:
slug: default-user-settings-flow slug: default-user-settings-flow
model: authentik_flows.flow model: authentik_flows.flow
@ -108,6 +109,8 @@ entries:
model: authentik_policies_expression.expressionpolicy model: authentik_policies_expression.expressionpolicy
- identifiers: - identifiers:
name: default-user-settings-write name: default-user-settings-write
attrs:
can_create_users: false
id: default-user-settings-write id: default-user-settings-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
- attrs: - attrs:

View File

@ -102,6 +102,8 @@ entries:
identifiers: identifiers:
name: default-password-change-write name: default-password-change-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs:
can_create_users: false
- attrs: - attrs:
evaluate_on_plan: true evaluate_on_plan: true
invalid_response_action: retry invalid_response_action: retry

View File

@ -12,6 +12,7 @@ entries:
name: Default enrollment Flow name: Default enrollment Flow
title: Welcome to authentik! title: Welcome to authentik!
designation: enrollment designation: enrollment
authentication: require_unauthenticated
- identifiers: - identifiers:
field_key: username field_key: username
label: Username label: Username
@ -94,7 +95,8 @@ entries:
name: default-enrollment-user-write name: default-enrollment-user-write
id: default-enrollment-user-write id: default-enrollment-user-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs: {} attrs:
can_create_users: true
- identifiers: - identifiers:
target: !KeyOf flow target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-first stage: !KeyOf default-enrollment-prompt-first

View File

@ -12,6 +12,7 @@ entries:
name: Default enrollment Flow name: Default enrollment Flow
title: Welcome to authentik! title: Welcome to authentik!
designation: enrollment designation: enrollment
authentication: require_unauthenticated
- identifiers: - identifiers:
field_key: username field_key: username
label: Username label: Username
@ -113,6 +114,7 @@ entries:
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs: attrs:
create_users_as_inactive: true create_users_as_inactive: true
can_create_users: true
- identifiers: - identifiers:
target: !KeyOf flow target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-first stage: !KeyOf default-enrollment-prompt-first

View File

@ -12,6 +12,7 @@ entries:
name: Default Authentication Flow name: Default Authentication Flow
title: Welcome to authentik! title: Welcome to authentik!
designation: authentication designation: authentication
authentication: require_unauthenticated
- identifiers: - identifiers:
name: test-not-app-password name: test-not-app-password
id: test-not-app-password id: test-not-app-password

View File

@ -12,6 +12,7 @@ entries:
name: Default Authentication Flow name: Default Authentication Flow
title: Welcome to authentik! title: Welcome to authentik!
designation: authentication designation: authentication
authentication: require_unauthenticated
- identifiers: - identifiers:
name: default-authentication-login name: default-authentication-login
id: default-authentication-login id: default-authentication-login

View File

@ -12,6 +12,7 @@ entries:
name: Default recovery flow name: Default recovery flow
title: Reset your password title: Reset your password
designation: recovery designation: recovery
authentication: require_unauthenticated
- identifiers: - identifiers:
field_key: password field_key: password
label: Password label: Password
@ -62,6 +63,8 @@ entries:
name: default-recovery-user-write name: default-recovery-user-write
id: default-recovery-user-write id: default-recovery-user-write
model: authentik_stages_user_write.userwritestage model: authentik_stages_user_write.userwritestage
attrs:
can_create_users: false
- identifiers: - identifiers:
name: default-recovery-identification name: default-recovery-identification
id: default-recovery-identification id: default-recovery-identification

View File

@ -12,6 +12,7 @@ entries:
name: Default unenrollment flow name: Default unenrollment flow
title: Delete your account title: Delete your account
designation: unenrollment designation: unenrollment
authentication: require_authenticated
- identifiers: - identifiers:
name: default-unenrollment-user-delete name: default-unenrollment-user-delete
id: default-unenrollment-user-delete id: default-unenrollment-user-delete

View File

@ -121,6 +121,15 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created"
],
"default": "present"
},
"attrs": { "attrs": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -32,7 +32,7 @@ services:
volumes: volumes:
- redis:/data - redis:/data
server: server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.11.0} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.11.4}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -52,7 +52,7 @@ services:
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
worker: worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.11.0} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.11.4}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
environment: environment:

2
go.mod
View File

@ -25,7 +25,7 @@ require (
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
goauthentik.io/api/v3 v3.2022101.8 goauthentik.io/api/v3 v3.2022110.1
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b

4
go.sum
View File

@ -376,8 +376,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
goauthentik.io/api/v3 v3.2022101.8 h1:s3dzv/4PQrvmmmxZ2Hte96CEmozk4vMHx7PSCzMi4Nw= goauthentik.io/api/v3 v3.2022110.1 h1:CCfFjQ1Ah/M+6CXZASlE2v6ug98D4qOigJL5fq1Uboc=
goauthentik.io/api/v3 v3.2022101.8/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10= goauthentik.io/api/v3 v3.2022110.1/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=

View File

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

View File

@ -100,7 +100,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
[tool.poetry] [tool.poetry]
name = "authentik" name = "authentik"
version = "2022.11.0" version = "2022.11.4"
description = "" description = ""
authors = ["authentik Team <hello@goauthentik.io>"] authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2022.11.0 version: 2022.11.4
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@goauthentik.io email: hello@goauthentik.io
@ -22331,6 +22331,10 @@ paths:
schema: schema:
type: string type: string
format: date-time format: date-time
- in: query
name: flow__slug
schema:
type: string
- in: query - in: query
name: name name: name
schema: schema:
@ -24574,6 +24578,10 @@ paths:
operationId: stages_user_write_list operationId: stages_user_write_list
description: UserWriteStage Viewset description: UserWriteStage Viewset
parameters: parameters:
- in: query
name: can_create_users
schema:
type: boolean
- in: query - in: query
name: create_users_as_inactive name: create_users_as_inactive
schema: schema:
@ -25269,6 +25277,13 @@ components:
- last_used - last_used
- user - user
- user_agent - user_agent
AuthenticationEnum:
enum:
- none
- require_authenticated
- require_unauthenticated
- require_superuser
type: string
AuthenticatorAttachmentEnum: AuthenticatorAttachmentEnum:
enum: enum:
- platform - platform
@ -27578,6 +27593,11 @@ components:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
description: Configure what should happen when a flow denies access to a description: Configure what should happen when a flow denies access to a
user. user.
authentication:
allOf:
- $ref: '#/components/schemas/AuthenticationEnum'
description: Required level of authentication and authorization to access
a flow.
required: required:
- background - background
- cache_count - cache_count
@ -27774,6 +27794,11 @@ components:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
description: Configure what should happen when a flow denies access to a description: Configure what should happen when a flow denies access to a
user. user.
authentication:
allOf:
- $ref: '#/components/schemas/AuthenticationEnum'
description: Required level of authentication and authorization to access
a flow.
required: required:
- designation - designation
- name - name
@ -28436,8 +28461,18 @@ components:
single_use: single_use:
type: boolean type: boolean
description: When enabled, the invitation will be deleted after usage. description: When enabled, the invitation will be deleted after usage.
flow:
type: string
format: uuid
nullable: true
description: When set, only the configured flow can use this invitation.
flow_obj:
allOf:
- $ref: '#/components/schemas/Flow'
readOnly: true
required: required:
- created_by - created_by
- flow_obj
- name - name
- pk - pk
InvitationRequest: InvitationRequest:
@ -28458,6 +28493,11 @@ components:
single_use: single_use:
type: boolean type: boolean
description: When enabled, the invitation will be deleted after usage. description: When enabled, the invitation will be deleted after usage.
flow:
type: string
format: uuid
nullable: true
description: When set, only the configured flow can use this invitation.
required: required:
- name - name
InvitationStage: InvitationStage:
@ -33651,6 +33691,11 @@ components:
- $ref: '#/components/schemas/DeniedActionEnum' - $ref: '#/components/schemas/DeniedActionEnum'
description: Configure what should happen when a flow denies access to a description: Configure what should happen when a flow denies access to a
user. user.
authentication:
allOf:
- $ref: '#/components/schemas/AuthenticationEnum'
description: Required level of authentication and authorization to access
a flow.
PatchedFlowStageBindingRequest: PatchedFlowStageBindingRequest:
type: object type: object
description: FlowStageBinding Serializer description: FlowStageBinding Serializer
@ -33798,6 +33843,11 @@ components:
single_use: single_use:
type: boolean type: boolean
description: When enabled, the invitation will be deleted after usage. description: When enabled, the invitation will be deleted after usage.
flow:
type: string
format: uuid
nullable: true
description: When set, only the configured flow can use this invitation.
PatchedInvitationStageRequest: PatchedInvitationStageRequest:
type: object type: object
description: InvitationStage Serializer description: InvitationStage Serializer
@ -35020,6 +35070,10 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Optionally add newly created users to this group. description: Optionally add newly created users to this group.
can_create_users:
type: boolean
description: When set, this stage can create users. If not enabled and no
user is available, stage will fail.
user_path_template: user_path_template:
type: string type: string
PatchedWebAuthnDeviceRequest: PatchedWebAuthnDeviceRequest:
@ -38226,6 +38280,10 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Optionally add newly created users to this group. description: Optionally add newly created users to this group.
can_create_users:
type: boolean
description: When set, this stage can create users. If not enabled and no
user is available, stage will fail.
user_path_template: user_path_template:
type: string type: string
required: required:
@ -38254,6 +38312,10 @@ components:
format: uuid format: uuid
nullable: true nullable: true
description: Optionally add newly created users to this group. description: Optionally add newly created users to this group.
can_create_users:
type: boolean
description: When set, this stage can create users. If not enabled and no
user is available, stage will fail.
user_path_template: user_path_template:
type: string type: string
required: required:

294
web/package-lock.json generated
View File

@ -21,7 +21,7 @@
"@codemirror/legacy-modes": "^6.3.0", "@codemirror/legacy-modes": "^6.3.0",
"@formatjs/intl-listformat": "^7.1.3", "@formatjs/intl-listformat": "^7.1.3",
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2022.10.1-1669049898", "@goauthentik/api": "^2022.11.3-1671801250",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.15.0", "@lingui/cli": "^3.15.0",
"@lingui/core": "^3.15.0", "@lingui/core": "^3.15.0",
@ -35,22 +35,22 @@
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.1", "@rollup/plugin-replace": "^5.0.1",
"@rollup/plugin-typescript": "^9.0.2", "@rollup/plugin-typescript": "^9.0.2",
"@sentry/browser": "^7.20.0", "@sentry/browser": "^7.20.1",
"@sentry/tracing": "^7.20.0", "@sentry/tracing": "^7.20.1",
"@squoosh/cli": "^0.7.2", "@squoosh/cli": "^0.7.2",
"@trivago/prettier-plugin-sort-imports": "^3.4.0", "@trivago/prettier-plugin-sort-imports": "^3.4.0",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.5", "@types/codemirror": "5.60.5",
"@types/grecaptcha": "^3.0.4", "@types/grecaptcha": "^3.0.4",
"@types/mermaid": "^9.1.0", "@types/mermaid": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.44.0",
"@webcomponents/webcomponentsjs": "^2.7.0", "@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.9.1", "chart.js": "^3.9.1",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0", "construct-style-sheets-polyfill": "^3.1.0",
"country-flag-icons": "^1.5.5", "country-flag-icons": "^1.5.5",
@ -1957,9 +1957,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2022.10.1-1669049898", "version": "2022.11.3-1671801250",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.10.1-1669049898.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.11.3-1671801250.tgz",
"integrity": "sha512-HzPCZ9HDZNCGk3wU/n/Z8jJ0KuRuWslxTL/Dq5n7vEPb2GYf6EvVQLGIIBPuWq4we9QpPRmgXJRtUYHjCxiSuw==" "integrity": "sha512-8N6C0IPCuZQ9y5crIDJ/AeC0VlxAa3lW9mFRZ4f8g2T9FIoZUqLuxF4Uy5WW1k+IK0M0JOkuVaj2iHipaGb5Zw=="
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.6", "version": "0.11.6",
@ -2944,13 +2944,13 @@
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.20.1.tgz",
"integrity": "sha512-L84CdB7DPQ2ohVcWh/KivemndWSZyXRvBZBr+tHFlQchzcaZZ/8lIPvjwvb8OJhzhecDq6JCAyUxaZwyItdyAg==", "integrity": "sha512-SE6mI4LkMzjEi5KB02Py24e2bKYZc/HZI/ZlTn36BuUQX/KYhzzKwzXucOJ5Qws9Ar9CViyKJDb07LxVQLYCGw==",
"dependencies": { "dependencies": {
"@sentry/core": "7.20.0", "@sentry/core": "7.20.1",
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2963,12 +2963,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.20.1.tgz",
"integrity": "sha512-8dIHk8niyEyVayUQpgECHnV2p444nPBjIyuQrtkdTxL7sBLC5+Y0DhRjxg9cJyZe/bZnXVerGkgcA7niKW4W8A==", "integrity": "sha512-Sc7vtNgO4QcE683qrR+b+KFQkkhvQv7gizN46QQPOWeqLDrai7x0+NspTFDLJyvdDuDh2rjoLfRwNsgbwe7Erw==",
"dependencies": { "dependencies": {
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2981,13 +2981,13 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.20.1.tgz",
"integrity": "sha512-qg3sMvjuMQl/NEaF8I2IpvUcJ4HGGVIwEqqqZ6hkeHXIKt02p6f+nls45pVhluMiNHAaQJ+vefMTUc3E1yZwDA==", "integrity": "sha512-LAiQcJMcOFkUwkGvqLghcVOtVVglHBQ2r7kRo75kqI0OTn/xMPRyPBGo94G+9zAKm+w7dGF5AUqq/4VUm7DJ+g==",
"dependencies": { "dependencies": {
"@sentry/core": "7.20.0", "@sentry/core": "7.20.1",
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3000,19 +3000,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.20.1.tgz",
"integrity": "sha512-x17ddduGWqW95neBFVvxzmInb5WXVw+2PcNASHXpGFhi7v2gz2a7/w2CcIKxsqODNnc+z/k1t0Y+uy9B6aH6ag==", "integrity": "sha512-bI4t5IXGLIQYH5MegKRl4x2LDSlPVbQJ5eE6NJCMrCm8PcFUo3WgkwP6toG9ThQwpTx/DhUo1sVNKrr0oW4cpA==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.20.1.tgz",
"integrity": "sha512-4lc122TFgkaCAvoPRy+uc5vgOCumTa/2nPkzCSxVsezQs+ebHxyMJQK7GWBLI6P+EzKfEjlgyMzRWaPJ3iJatA==", "integrity": "sha512-wToW0710OijQLUZnbbOx1pxwJ4mXUZ5ZFl4/x7ubNftkOz5NwJ+F3ylRqHXpZJaR9pUfR5CNdInTFZn05h/KeQ==",
"dependencies": { "dependencies": {
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3365,13 +3365,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
"integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/type-utils": "5.43.0", "@typescript-eslint/type-utils": "5.44.0",
"@typescript-eslint/utils": "5.43.0", "@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@ -3411,13 +3411,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
"integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -3437,12 +3437,12 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
"integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.43.0" "@typescript-eslint/visitor-keys": "5.44.0"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3453,12 +3453,12 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz",
"integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"@typescript-eslint/utils": "5.43.0", "@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
@ -3479,9 +3479,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
"integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}, },
@ -3491,12 +3491,12 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
"integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.43.0", "@typescript-eslint/visitor-keys": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -3531,15 +3531,15 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz",
"integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -3570,11 +3570,11 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
"integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"engines": { "engines": {
@ -4185,11 +4185,11 @@
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
}, },
"node_modules/chartjs-adapter-moment": { "node_modules/chartjs-adapter-moment": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.0.tgz", "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz",
"integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA==", "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==",
"peerDependencies": { "peerDependencies": {
"chart.js": "^3.0.0", "chart.js": ">=3.0.0",
"moment": "^2.10.2" "moment": "^2.10.2"
} }
}, },
@ -11667,9 +11667,9 @@
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==" "integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
}, },
"@goauthentik/api": { "@goauthentik/api": {
"version": "2022.10.1-1669049898", "version": "2022.11.3-1671801250",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.10.1-1669049898.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.11.3-1671801250.tgz",
"integrity": "sha512-HzPCZ9HDZNCGk3wU/n/Z8jJ0KuRuWslxTL/Dq5n7vEPb2GYf6EvVQLGIIBPuWq4we9QpPRmgXJRtUYHjCxiSuw==" "integrity": "sha512-8N6C0IPCuZQ9y5crIDJ/AeC0VlxAa3lW9mFRZ4f8g2T9FIoZUqLuxF4Uy5WW1k+IK0M0JOkuVaj2iHipaGb5Zw=="
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.11.6", "version": "0.11.6",
@ -12398,13 +12398,13 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.20.1.tgz",
"integrity": "sha512-L84CdB7DPQ2ohVcWh/KivemndWSZyXRvBZBr+tHFlQchzcaZZ/8lIPvjwvb8OJhzhecDq6JCAyUxaZwyItdyAg==", "integrity": "sha512-SE6mI4LkMzjEi5KB02Py24e2bKYZc/HZI/ZlTn36BuUQX/KYhzzKwzXucOJ5Qws9Ar9CViyKJDb07LxVQLYCGw==",
"requires": { "requires": {
"@sentry/core": "7.20.0", "@sentry/core": "7.20.1",
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12416,12 +12416,12 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.20.1.tgz",
"integrity": "sha512-8dIHk8niyEyVayUQpgECHnV2p444nPBjIyuQrtkdTxL7sBLC5+Y0DhRjxg9cJyZe/bZnXVerGkgcA7niKW4W8A==", "integrity": "sha512-Sc7vtNgO4QcE683qrR+b+KFQkkhvQv7gizN46QQPOWeqLDrai7x0+NspTFDLJyvdDuDh2rjoLfRwNsgbwe7Erw==",
"requires": { "requires": {
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12433,13 +12433,13 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.20.1.tgz",
"integrity": "sha512-qg3sMvjuMQl/NEaF8I2IpvUcJ4HGGVIwEqqqZ6hkeHXIKt02p6f+nls45pVhluMiNHAaQJ+vefMTUc3E1yZwDA==", "integrity": "sha512-LAiQcJMcOFkUwkGvqLghcVOtVVglHBQ2r7kRo75kqI0OTn/xMPRyPBGo94G+9zAKm+w7dGF5AUqq/4VUm7DJ+g==",
"requires": { "requires": {
"@sentry/core": "7.20.0", "@sentry/core": "7.20.1",
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"@sentry/utils": "7.20.0", "@sentry/utils": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12451,16 +12451,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.20.1.tgz",
"integrity": "sha512-x17ddduGWqW95neBFVvxzmInb5WXVw+2PcNASHXpGFhi7v2gz2a7/w2CcIKxsqODNnc+z/k1t0Y+uy9B6aH6ag==" "integrity": "sha512-bI4t5IXGLIQYH5MegKRl4x2LDSlPVbQJ5eE6NJCMrCm8PcFUo3WgkwP6toG9ThQwpTx/DhUo1sVNKrr0oW4cpA=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "7.20.0", "version": "7.20.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.20.1.tgz",
"integrity": "sha512-4lc122TFgkaCAvoPRy+uc5vgOCumTa/2nPkzCSxVsezQs+ebHxyMJQK7GWBLI6P+EzKfEjlgyMzRWaPJ3iJatA==", "integrity": "sha512-wToW0710OijQLUZnbbOx1pxwJ4mXUZ5ZFl4/x7ubNftkOz5NwJ+F3ylRqHXpZJaR9pUfR5CNdInTFZn05h/KeQ==",
"requires": { "requires": {
"@sentry/types": "7.20.0", "@sentry/types": "7.20.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -12776,13 +12776,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
"integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/type-utils": "5.43.0", "@typescript-eslint/type-utils": "5.44.0",
"@typescript-eslint/utils": "5.43.0", "@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@ -12802,48 +12802,48 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
"integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"debug": "^4.3.4" "debug": "^4.3.4"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
"integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
"requires": { "requires": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.43.0" "@typescript-eslint/visitor-keys": "5.44.0"
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz",
"integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==",
"requires": { "requires": {
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"@typescript-eslint/utils": "5.43.0", "@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
"integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==" "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ=="
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
"integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
"requires": { "requires": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.43.0", "@typescript-eslint/visitor-keys": "5.44.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -12862,15 +12862,15 @@
} }
}, },
"@typescript-eslint/utils": { "@typescript-eslint/utils": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz",
"integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==",
"requires": { "requires": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.43.0", "@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.43.0", "@typescript-eslint/typescript-estree": "5.44.0",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -12887,11 +12887,11 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "5.43.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
"integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
"requires": { "requires": {
"@typescript-eslint/types": "5.43.0", "@typescript-eslint/types": "5.44.0",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"dependencies": { "dependencies": {
@ -13325,9 +13325,9 @@
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
}, },
"chartjs-adapter-moment": { "chartjs-adapter-moment": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.0.tgz", "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz",
"integrity": "sha512-PqlerEvQcc5hZLQ/NQWgBxgVQ4TRdvkW3c/t+SUEQSj78ia3hgLkf2VZ2yGJtltNbEEFyYGm+cA6XXevodYvWA==", "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==",
"requires": {} "requires": {}
}, },
"chokidar": { "chokidar": {

View File

@ -64,7 +64,7 @@
"@codemirror/legacy-modes": "^6.3.0", "@codemirror/legacy-modes": "^6.3.0",
"@formatjs/intl-listformat": "^7.1.3", "@formatjs/intl-listformat": "^7.1.3",
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2022.10.1-1669049898", "@goauthentik/api": "^2022.11.3-1671801250",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.15.0", "@lingui/cli": "^3.15.0",
"@lingui/core": "^3.15.0", "@lingui/core": "^3.15.0",
@ -78,22 +78,22 @@
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.1", "@rollup/plugin-replace": "^5.0.1",
"@rollup/plugin-typescript": "^9.0.2", "@rollup/plugin-typescript": "^9.0.2",
"@sentry/browser": "^7.20.0", "@sentry/browser": "^7.20.1",
"@sentry/tracing": "^7.20.0", "@sentry/tracing": "^7.20.1",
"@squoosh/cli": "^0.7.2", "@squoosh/cli": "^0.7.2",
"@trivago/prettier-plugin-sort-imports": "^3.4.0", "@trivago/prettier-plugin-sort-imports": "^3.4.0",
"@types/chart.js": "^2.9.37", "@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.5", "@types/codemirror": "5.60.5",
"@types/grecaptcha": "^3.0.4", "@types/grecaptcha": "^3.0.4",
"@types/mermaid": "^9.1.0", "@types/mermaid": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.44.0",
"@webcomponents/webcomponentsjs": "^2.7.0", "@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.9.1", "chart.js": "^3.9.1",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0", "construct-style-sheets-polyfill": "^3.1.0",
"country-flag-icons": "^1.5.5", "country-flag-icons": "^1.5.5",

View File

@ -1,4 +1,4 @@
Contact: mailto:security@goauthentik.io Contact: mailto:security@goauthentik.io
Expires: Sat, 1 Jan 2023 00:00 +0200 Expires: Mon, 1 Jan 2024 00:00 +0200
Preferred-Languages: en, de Preferred-Languages: en, de
Policy: https://github.com/goauthentik/authentik/blob/main/SECURITY.md Policy: https://github.com/goauthentik/authentik/blob/main/SECURITY.md

View File

@ -1,4 +1,5 @@
import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils"; import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils";
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -141,6 +142,37 @@ export class FlowForm extends ModelForm<Flow, string> {
</option>`; </option>`;
} }
renderAuthentication(): TemplateResult {
return html`
<option
value=${AuthenticationEnum.None}
?selected=${this.instance?.authentication === AuthenticationEnum.None}
>
${t`No requirement`}
</option>
<option
value=${AuthenticationEnum.RequireAuthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireAuthenticated}
>
${t`Require authentication`}
</option>
<option
value=${AuthenticationEnum.RequireUnauthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireUnauthenticated}
>
${t`Require no authentication.`}
</option>
<option
value=${AuthenticationEnum.RequireSuperuser}
?selected=${this.instance?.authentication === AuthenticationEnum.RequireSuperuser}
>
${t`Require superuser.`}
</option>
`;
}
renderLayout(): TemplateResult { renderLayout(): TemplateResult {
return html` return html`
<option <option
@ -224,6 +256,18 @@ export class FlowForm extends ModelForm<Flow, string> {
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authentication`}
?required=${true}
name="authentication"
>
<select class="pf-c-form-control">
${this.renderAuthentication()}
</select>
<p class="pf-c-form__helper-text">
${t`Required authentication level for this flow.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Designation`} label=${t`Designation`}
?required=${true} ?required=${true}

View File

@ -72,6 +72,9 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
send = (data: ProxyProvider): Promise<ProxyProvider> => { send = (data: ProxyProvider): Promise<ProxyProvider> => {
data.mode = this.mode; data.mode = this.mode;
if (this.mode !== ProxyMode.ForwardDomain) {
data.cookieDomain = "";
}
if (this.instance) { if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({ return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
id: this.instance.pk || 0, id: this.instance.pk || 0,

View File

@ -16,9 +16,9 @@ import { until } from "lit/directives/until.js";
import { import {
AuthenticatorDuoStage, AuthenticatorDuoStage,
AuthenticatorDuoStageManualDeviceImportRequest,
CoreApi, CoreApi,
StagesApi, StagesApi,
StagesAuthenticatorDuoImportDeviceManualCreateRequest,
} from "@goauthentik/api"; } from "@goauthentik/api";
@customElement("ak-stage-authenticator-duo-device-import-form") @customElement("ak-stage-authenticator-duo-device-import-form")
@ -34,11 +34,11 @@ export class DuoDeviceImportForm extends ModelForm<AuthenticatorDuoStage, string
} }
send = (data: AuthenticatorDuoStage): Promise<void> => { send = (data: AuthenticatorDuoStage): Promise<void> => {
const importData = data as unknown as StagesAuthenticatorDuoImportDeviceManualCreateRequest; const importData = data as unknown as AuthenticatorDuoStageManualDeviceImportRequest;
importData.stageUuid = this.instancePk; return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoImportDeviceManualCreate({
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoImportDeviceManualCreate( stageUuid: this.instance?.pk || "",
importData, authenticatorDuoStageManualDeviceImportRequest: importData,
); });
}; };
renderForm(): TemplateResult { renderForm(): TemplateResult {

View File

@ -1,5 +1,4 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";

View File

@ -9,8 +9,15 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { Invitation, StagesApi } from "@goauthentik/api"; import {
FlowsApi,
FlowsInstancesListDesignationEnum,
Invitation,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-invitation-form") @customElement("ak-invitation-form")
export class InvitationForm extends ModelForm<Invitation, string> { export class InvitationForm extends ModelForm<Invitation, string> {
@ -66,6 +73,34 @@ export class InvitationForm extends ModelForm<Invitation, string> {
value="${dateTimeLocal(first(this.instance?.expires, new Date()))}" value="${dateTimeLocal(first(this.instance?.expires, new Date()))}"
/> />
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Flow`} ?required=${true} name="flow">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.flow === undefined}>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.flow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`When selected, the invite will only be usable with the flow. By default the invite is accepted on all flows with invitation stages.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Attributes`} name="fixedData"> <ak-form-element-horizontal label=${t`Attributes`} name="fixedData">
<ak-codemirror <ak-codemirror
mode="yaml" mode="yaml"

View File

@ -14,12 +14,12 @@ import PFFormControl from "@patternfly/patternfly/components/FormControl/form-co
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { StagesApi } from "@goauthentik/api"; import { Invitation, StagesApi } from "@goauthentik/api";
@customElement("ak-stage-invitation-list-link") @customElement("ak-stage-invitation-list-link")
export class InvitationListLink extends AKElement { export class InvitationListLink extends AKElement {
@property() @property({ attribute: false })
invitation?: string; invitation?: Invitation;
@property() @property()
selectedFlow?: string; selectedFlow?: string;
@ -29,60 +29,67 @@ export class InvitationListLink extends AKElement {
} }
renderLink(): string { renderLink(): string {
return `${window.location.protocol}//${window.location.host}/if/flow/${this.selectedFlow}/?itoken=${this.invitation}`; if (this.invitation?.flowObj) {
this.selectedFlow = this.invitation.flowObj?.slug;
}
return `${window.location.protocol}//${window.location.host}/if/flow/${this.selectedFlow}/?itoken=${this.invitation?.pk}`;
}
renderFlowSelector(): TemplateResult {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Select an enrollment flow`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.selectedFlow = current;
}}
>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesInvitationStagesList({
ordering: "name",
noFlows: false,
})
.then((stages) => {
if (
!this.selectedFlow &&
stages.results.length > 0 &&
stages.results[0].flowSet
) {
this.selectedFlow = stages.results[0].flowSet[0].slug;
}
const seenFlowSlugs: string[] = [];
return stages.results.map((stage) => {
return stage.flowSet?.map((flow) => {
if (seenFlowSlugs.includes(flow.slug)) {
return html``;
}
seenFlowSlugs.push(flow.slug);
return html`<option
value=${flow.slug}
?selected=${flow.slug === this.selectedFlow}
>
${flow.slug}
</option>`;
});
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</div>
</dd>
</div>`;
} }
render(): TemplateResult { render(): TemplateResult {
return html`<dl class="pf-c-description-list pf-m-horizontal"> return html`<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group"> ${this.invitation?.flow === undefined ? this.renderFlowSelector() : html``}
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Select an enrollment flow`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.selectedFlow = current;
}}
>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesInvitationStagesList({
ordering: "name",
noFlows: false,
})
.then((stages) => {
if (
!this.selectedFlow &&
stages.results.length > 0 &&
stages.results[0].flowSet
) {
this.selectedFlow = stages.results[0].flowSet[0].slug;
}
const seenFlowSlugs: string[] = [];
return stages.results.map((stage) => {
return stage.flowSet?.map((flow) => {
if (seenFlowSlugs.includes(flow.slug)) {
return html``;
}
seenFlowSlugs.push(flow.slug);
return html`<option
value=${flow.slug}
?selected=${flow.slug === this.selectedFlow}
>
${flow.slug}
</option>`;
});
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</div>
</dd>
</div>
<div class="pf-c-description-list__group"> <div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term"> <dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text" <span class="pf-c-description-list__text"

View File

@ -2,6 +2,7 @@ import "@goauthentik/admin/stages/invitation/InvitationForm";
import "@goauthentik/admin/stages/invitation/InvitationListLink"; import "@goauthentik/admin/stages/invitation/InvitationListLink";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { PFColor } from "@goauthentik/elements/Label";
import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/DeleteBulkForm";
@ -18,7 +19,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import { Invitation, StagesApi } from "@goauthentik/api"; import { FlowDesignationEnum, Invitation, StagesApi } from "@goauthentik/api";
@customElement("ak-stage-invitation-list") @customElement("ak-stage-invitation-list")
export class InvitationListPage extends TablePage<Invitation> { export class InvitationListPage extends TablePage<Invitation> {
@ -49,12 +50,24 @@ export class InvitationListPage extends TablePage<Invitation> {
@state() @state()
invitationStageExists = false; invitationStageExists = false;
@state()
multipleEnrollmentFlows = false;
async apiEndpoint(page: number): Promise<PaginatedResponse<Invitation>> { async apiEndpoint(page: number): Promise<PaginatedResponse<Invitation>> {
// Check if any invitation stages exist
const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({ const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({
noFlows: false, noFlows: false,
}); });
this.invitationStageExists = stages.pagination.count > 0; this.invitationStageExists = stages.pagination.count > 0;
this.expandable = this.invitationStageExists; this.expandable = this.invitationStageExists;
stages.results.forEach((stage) => {
const enrollmentFlows = (stage.flowSet || []).filter(
(flow) => flow.designation === FlowDesignationEnum.Enrollment,
);
if (enrollmentFlows.length > 1) {
this.multipleEnrollmentFlows = true;
}
});
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({ return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({
ordering: this.order, ordering: this.order,
page: page, page: page,
@ -96,7 +109,14 @@ export class InvitationListPage extends TablePage<Invitation> {
row(item: Invitation): TemplateResult[] { row(item: Invitation): TemplateResult[] {
return [ return [
html`${item.name}`, html`<div>${item.name}</div>
${!item.flowObj && this.multipleEnrollmentFlows
? html`
<ak-label color=${PFColor.Orange}>
${t`Invitation not limited to any flow, and can be used with any enrollment flow.`}
</ak-label>
`
: html``}`,
html`${item.createdBy?.username}`, html`${item.createdBy?.username}`,
html`${item.expires?.toLocaleString() || t`-`}`, html`${item.expires?.toLocaleString() || t`-`}`,
html` <ak-forms-modal> html` <ak-forms-modal>
@ -114,7 +134,7 @@ export class InvitationListPage extends TablePage<Invitation> {
return html` <td role="cell" colspan="3"> return html` <td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content"> <div class="pf-c-table__expandable-row-content">
<ak-stage-invitation-list-link <ak-stage-invitation-list-link
invitation=${item.pk} .invitation=${item}
></ak-stage-invitation-list-link> ></ak-stage-invitation-list-link>
</div> </div>
</td> </td>

View File

@ -59,6 +59,21 @@ export class UserWriteStageForm extends ModelForm<UserWriteStage, string> {
<ak-form-group .expanded=${true}> <ak-form-group .expanded=${true}>
<span slot="header"> ${t`Stage-specific settings`} </span> <span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="canCreateUsers">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.canCreateUsers, false)}
/>
<label class="pf-c-check__label">
${t`Can create users`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`When enabled, this stage has the ability to create new users. If no user is available in the flow with this disabled, the stage will fail.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="createUsersAsInactive"> <ak-form-element-horizontal name="createUsersAsInactive">
<div class="pf-c-check"> <div class="pf-c-check">
<input <input

View File

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

View File

@ -98,13 +98,6 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost {
if (!this.flowSlug) { if (!this.flowSlug) {
return; return;
} }
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesExecuteRetrieve({
slug: this.flowSlug || "",
})
.then(() => {
this.nextChallenge();
});
}); });
} }

View File

@ -24,4 +24,4 @@ This export can be triggered via the API or the Web UI by clicking the download
## Cleaning up ## Cleaning up
Exports from either method will contain a (potentially) long list of objects, all with hardcoded primary keys and now ability for templating/instantiation. This is because currently, authentik does not check which primary keys are used where. It is assumed that for most exports, there'll be some manual changes done regardless, to filter out unwanted objects, adjust properties, etc. Exports from either method will contain a (potentially) long list of objects, all with hardcoded primary keys and no ability for templating/instantiation. This is because currently, authentik does not check which primary keys are used where. It is assumed that for most exports, there'll be some manual changes done regardless, to filter out unwanted objects, adjust properties, etc.

View File

@ -20,6 +20,11 @@ context:
entries: entries:
- # Model in app.model notation, possibilities are listed in the schema (required) - # Model in app.model notation, possibilities are listed in the schema (required)
model: authentik_flows.flow model: authentik_flows.flow
# The state this object should be in (optional, can be "present", "created" or "absent")
# Present will keep the object in sync with its definition here, created will only ensure
# the object is created (and create it with the values given here), and "absent" will
# delete the object
state: present
# Key:value filters to uniquely identify this object (required) # Key:value filters to uniquely identify this object (required)
identifiers: identifiers:
slug: initial-setup slug: initial-setup

View File

@ -62,6 +62,15 @@ image:
- web: fix twitter icon - web: fix twitter icon
- web/flows: always hide static user info when its not set in the flow - web/flows: always hide static user info when its not set in the flow
## Fixed in 2022.11.1
- blueprints: add desired state attribute to objects (#4061)
- core: fix tab-complete in shell
- root: fix build on arm64
- stages/email: add test for email translation
- web/admin: fix error when importing duo devices
- web/admin: reset cookie_domain when setting non-domain forward auth
## API Changes ## API Changes
#### What's Changed #### What's Changed

View File

@ -0,0 +1,29 @@
# CVE-2022-23555
## Token reuse in invitation URLs leads to access control bypass via the use of a different enrollment flow
### Summary
Token reuse in invitation URLs leads to access control bypass via the use of a different enrollment flow than in the one provided.
### Patches
authentik 2022.11.4, 2022.10.4 and 2022.12.0 fix this issue, for other versions the workaround can be used.
### Impact
Only configurations using both invitations and have multiple enrollment flows with invitation stages that grant different permissions are affected. The default configuration is not vulnerable, and neither are configurations with a single enrollment flow.
### Details
The vulnerability allows an attacker that knows different invitation flows names (e.g. `enrollment-invitation-test` and `enrollment-invitation-admin`) via either different invite links or via brute forcing to signup via a single invitation url for any valid invite link received (it can even be a url for a third flow as long as it's a valid invite) as the token used in the `Invitations` section of the Admin interface does NOT change when a different `enrollment flow` is selected via the interface and it is NOT bound to the selected flow, so it will be valid for any flow when used.
### Workarounds
As a workaround, fixed data can be added to invitations which can be checked in the flow to deny requests. Alternatively, an identifier with high entropy (like a UUID) can be used as flow slug, mitigating the attack vector by exponentially decreasing the possibility of discovering other flows.
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@ -0,0 +1,19 @@
# CVE-2022-46145
## Unauthorized user creation and potential account takeover
### Impact
With the default flows, unauthenticated users can create new accounts in authentik. If a flow exists that allows for email-verified password recovery, this can be used to overwrite the email address of admin accounts and take over their accounts
### Patches
authentik 2022.11.2 and 2022.10.2 fix this issue, for other versions the workaround can be used.
### Workarounds
A policy can be created and bound to the `default-user-settings-flow` flow with the following contents
```python
return request.user.is_authenticated
```

View File

@ -0,0 +1,25 @@
# CVE-2022-46172
## Existing Authenticated Users can Create Arbitrary Accounts
### Summary
Any authenticated user can create an arbitrary number of accounts through the default flows. This would circumvent any policy in a situation where it is undesirable for users to create new accounts by themselves. This may also have carry over consequences to other applications being how these new basic accounts would exist throughout the SSO infrastructure. By default the newly created accounts cannot be logged into as no password reset exists by default. However password resets are likely to be enabled by most installations.
### Patches
authentik 2022.11.4, 2022.10.4 and 2022.12.0 fix this issue.
### Impact
This vulnerability could make it much easier for name and email collisions to occur, making it harder for user to log in. This also makes it more difficult for admins to properly administer users since more and more confusing users will exist. This paired with password reset flows if enabled would mean a circumvention of on-boarding policies. Say for instance a company wanted to invite a limited number of beta testers, those beta testers would be able to create an arbitrary number of accounts themselves.
### Details
This vulnerability has already been submitted over email, this security advisory serves as formalization towards broader information dissemination. This vulnerability pertains to the user context used in the default-user-settings-flow. /api/v3/flows/instances/default-user-settings-flow/execute/
### For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@ -0,0 +1,5 @@
# Security Policy
import SecurityPolicy from "../../../SECURITY.md";
<SecurityPolicy />

View File

@ -95,7 +95,7 @@ module.exports = {
}, },
{ {
label: "Installations", label: "Installations",
to: "docs/installation/index", to: "docs/installation/",
}, },
], ],
}, },

View File

@ -282,5 +282,20 @@ module.exports = {
"troubleshooting/missing_admin_group", "troubleshooting/missing_admin_group",
], ],
}, },
{
type: "category",
label: "Security",
link: {
type: "generated-index",
title: "Security",
slug: "security",
},
items: [
"security/policy",
"security/CVE-2022-46145",
"security/CVE-2022-46172",
"security/CVE-2022-23555",
],
},
], ],
}; };