Compare commits

..

275 Commits

Author SHA1 Message Date
8543e140ef stages/consent: fix error when requests with identical empty permissions
closes #3280

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-08-01 20:58:25 +02:00
e2cf578afd root: use updated schema
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-08-01 10:11:06 +02:00
0ce9fd9b2e web: fix updates
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-08-01 09:59:57 +02:00
d17ad65435 web: bump api client to match
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-31 23:54:29 +02:00
01529d3894 flows/stages/consent: fix for post requests (#3339)
add unique token to consent stage to ensure it is shown

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-31 23:53:06 +02:00
dae6493a3e release: 2022.7.3 2022-07-20 09:37:43 +02:00
ad07984158 website/docs: prepare 2022.7.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-19 21:40:56 +02:00
f909b86338 stages/consent: fix permimssions for consent API (allow owner to delete) 2022-07-19 16:41:34 +00:00
327df6529b sources/oauth: use oidc preferred_username if set, otherwise nickname 2022-07-19 16:41:10 +00:00
658dc63c4c lifecycle: revert waiting for lock, launch managed reconcile on app import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-19 12:06:57 +02:00
4edec5f666 lifecycle: connect to database first
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-19 10:54:56 +02:00
d150a0c135 web: bump @typescript-eslint/eslint-plugin from 5.30.6 to 5.30.7 in /web (#3278) 2022-07-19 08:48:26 +02:00
d4242781a0 web: bump @babel/plugin-transform-runtime from 7.18.6 to 7.18.9 in /web (#3277) 2022-07-19 08:48:09 +02:00
7369ca0b25 web: bump @babel/plugin-proposal-decorators from 7.18.6 to 7.18.9 in /web (#3273) 2022-07-19 08:46:24 +02:00
561f427cc5 web: bump @typescript-eslint/parser from 5.30.6 to 5.30.7 in /web (#3274) 2022-07-19 08:46:14 +02:00
8049ab703a web: bump @trivago/prettier-plugin-sort-imports from 3.2.0 to 3.3.0 in /web (#3275) 2022-07-19 08:46:02 +02:00
9c2a97263a web: bump @babel/core from 7.18.6 to 7.18.9 in /web (#3276) 2022-07-19 08:45:35 +02:00
345504c1a4 web: bump @babel/preset-env from 7.18.6 to 7.18.9 in /web (#3279) 2022-07-19 08:45:00 +02:00
549f6f2077 providers/oauth2: correctly log authenticated user for OAuth views using protected_resource_view
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-18 22:20:09 +02:00
35c6decc75 web: bump @sentry/tracing from 7.6.0 to 7.7.0 in /web (#3266)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.6.0 to 7.7.0.
- [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.6.0...7.7.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:49:46 +02:00
b3abeb78ff website: bump react-toggle from 4.1.2 to 4.1.3 in /website (#3263)
Bumps [react-toggle](https://github.com/aaronshaf/react-toggle) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/aaronshaf/react-toggle/releases)
- [Changelog](https://github.com/aaronshaf/react-toggle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/aaronshaf/react-toggle/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:48:59 +02:00
0562a1ad42 web: bump @sentry/browser from 7.6.0 to 7.7.0 in /web (#3264)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.6.0 to 7.7.0.
- [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.6.0...7.7.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:48:50 +02:00
febb0920fd web: bump rollup from 2.76.0 to 2.77.0 in /web (#3265)
Bumps [rollup](https://github.com/rollup/rollup) from 2.76.0 to 2.77.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.76.0...v2.77.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:48:40 +02:00
549662beb0 web: bump eslint from 8.19.0 to 8.20.0 in /web (#3267)
Bumps [eslint](https://github.com/eslint/eslint) from 8.19.0 to 8.20.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.19.0...v8.20.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:48:28 +02:00
1ea4440c5d core: bump sentry-sdk from 1.7.1 to 1.7.2 (#3268)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.7.1...1.7.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 10:48:19 +02:00
787abdff5b core: bump pylint from 2.14.4 to 2.14.5 (#3269)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.14.4 to 2.14.5.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.14.4...v2.14.5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 09:29:57 +02:00
2237807241 core: bump github.com/go-openapi/strfmt from 0.21.2 to 0.21.3 (#3270)
Bumps [github.com/go-openapi/strfmt](https://github.com/go-openapi/strfmt) from 0.21.2 to 0.21.3.
- [Release notes](https://github.com/go-openapi/strfmt/releases)
- [Commits](https://github.com/go-openapi/strfmt/compare/v0.21.2...v0.21.3)

---
updated-dependencies:
- dependency-name: github.com/go-openapi/strfmt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 09:29:43 +02:00
e9d9d658c4 lifecycle: make worker wait for migrations to be done (#3254)
* lifecycle: make worker wait for migrations to be done

* retry managed reconcile task
2022-07-15 19:44:45 +02:00
e704092d19 website: bump @docusaurus/plugin-client-redirects from 2.0.0-beta.22 to 2.0.0-rc.1 in /website (#3260)
website: bump @docusaurus/plugin-client-redirects in /website

Bumps [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) from 2.0.0-beta.22 to 2.0.0-rc.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-rc.1/packages/docusaurus-plugin-client-redirects)

---
updated-dependencies:
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-15 09:16:08 +02:00
305f72c197 website: bump @docusaurus/preset-classic from 2.0.0-beta.22 to 2.0.0-rc.1 in /website (#3259) 2022-07-15 09:01:28 +02:00
fb6b6b4476 ci: bump actions/setup-node from 3.4.0 to 3.4.1 (#3261) 2022-07-15 09:00:44 +02:00
791cc74dbb core: bump golang from 1.18.3-bullseye to 1.18.4-bullseye (#3255)
Bumps golang from 1.18.3-bullseye to 1.18.4-bullseye.

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-14 10:17:48 +02:00
41f139589c core: bump webauthn from 1.5.2 to 1.6.0 (#3256)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 1.5.2 to 1.6.0.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v1.5.2...v1.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-14 10:16:14 +02:00
df24e3020b core: bump sentry-sdk from 1.7.0 to 1.7.1 (#3257) 2022-07-14 09:12:58 +02:00
e44c716cbe website/integrations: add note for rancher idp initiated
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-13 23:14:01 +02:00
d35302923d core: bump coverage from 6.4.1 to 6.4.2 (#3251)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4.1...6.4.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-13 21:06:06 +02:00
4d928368bc core: bump channels-redis from 3.4.0 to 3.4.1 (#3252)
Bumps [channels-redis](https://github.com/django/channels_redis) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/django/channels_redis/releases)
- [Changelog](https://github.com/django/channels_redis/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/channels_redis/compare/3.4.0...3.4.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-13 09:59:52 +02:00
c055d7a470 web: bump @typescript-eslint/parser from 5.30.5 to 5.30.6 in /web (#3244) 2022-07-12 09:22:46 +02:00
9e1b49e181 web: bump @typescript-eslint/eslint-plugin from 5.30.5 to 5.30.6 in /web (#3245) 2022-07-12 09:21:29 +02:00
db6a9ede1b ci: bump actions/setup-node from 3.3.0 to 3.4.0 (#3247) 2022-07-12 09:21:16 +02:00
86df0a448e core: bump sentry-sdk from 1.6.0 to 1.7.0 (#3246) 2022-07-12 09:20:42 +02:00
5ec052bd92 website/integrations: Node-Red integration with openidconnect (#3221)
* add Node-Red integration doc

* Node-Red Protocol settings + linting fixes
2022-07-11 10:59:57 +02:00
6f7984d05a website: bump @docusaurus/plugin-client-redirects from 2.0.0-beta.21 to 2.0.0-beta.22 in /website (#3239)
website: bump @docusaurus/plugin-client-redirects in /website

Bumps [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) from 2.0.0-beta.21 to 2.0.0-beta.22.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.22/packages/docusaurus-plugin-client-redirects)

---
updated-dependencies:
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 09:35:15 +02:00
f6d64d1d4b website: bump @docusaurus/preset-classic from 2.0.0-beta.21 to 2.0.0-beta.22 in /website (#3242)
website: bump @docusaurus/preset-classic in /website

Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 2.0.0-beta.21 to 2.0.0-beta.22.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.22/packages/docusaurus-preset-classic)

---
updated-dependencies:
- dependency-name: "@docusaurus/preset-classic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 09:34:12 +02:00
ef0c7a5a57 web: bump @sentry/browser from 7.5.1 to 7.6.0 in /web (#3238)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.5.1 to 7.6.0.
- [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.5.1...7.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 09:33:52 +02:00
34dfbf8e9e web: bump @sentry/tracing from 7.5.1 to 7.6.0 in /web (#3240)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.5.1 to 7.6.0.
- [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.5.1...7.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 09:31:41 +02:00
71d38e6fd0 web: bump rollup from 2.75.7 to 2.76.0 in /web (#3241) 2022-07-11 09:27:53 +02:00
9a9ba2560b core: delete expired models when filtering instead of excluding them
closes #3233

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-09 13:40:39 +02:00
2432e51970 web/elements: improve contrast for codemirror backgrounds
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-08 22:58:39 +02:00
47434cd62d stages/prompt: try to base64 decode file, fallback to keeping value as-is
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-08 22:45:31 +02:00
ff500b44a6 stages/prompt: force required to false when using readonlyfield
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-08 22:38:37 +02:00
4c14c7f3a4 web: bump @sentry/browser from 7.5.0 to 7.5.1 in /web (#3230) 2022-07-07 09:44:22 +02:00
019c4bf182 web: bump @sentry/tracing from 7.5.0 to 7.5.1 in /web (#3231) 2022-07-07 09:27:17 +02:00
2cbc291f04 core: bump goauthentik.io/api/v3 from 3.2022071.2 to 3.2022072.1 (#3232) 2022-07-07 09:27:01 +02:00
5197a3a461 web: bump moment from 2.29.3 to 2.29.4 in /web (#3226)
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 22:18:05 +02:00
52be87785f web: Update Web API Client version (#3227)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-07-06 22:07:21 +02:00
8e19fb3a8c release: 2022.7.2 2022-07-06 20:31:48 +02:00
0448dcf655 website/docs: prepare 2022.7.2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-06 19:17:15 +02:00
b8f74ab9e7 website: bump clsx from 1.2.0 to 1.2.1 in /website (#3222)
Bumps [clsx](https://github.com/lukeed/clsx) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/lukeed/clsx/releases)
- [Commits](https://github.com/lukeed/clsx/compare/v1.2.0...v1.2.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 09:54:12 +02:00
501ce5cebb core: bump goauthentik.io/api/v3 from 3.2022071.1 to 3.2022071.2 (#3223)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022071.1 to 3.2022071.2.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022071.1...v3.2022071.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 09:54:02 +02:00
b896ca7ef6 web: fix locale erroring with no pre-hydrated locale setting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-06 09:53:46 +02:00
d497db3010 flows: fix OOB flow incorrectly setting pending user
closes #3224

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-06 09:51:20 +02:00
24f95fdeaa tenants: fix tests for current tenant
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-05 23:47:49 +02:00
d1c4818724 policies: improve api test coverage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-05 23:20:48 +02:00
9f736a9d99 web: Update Web API Client version (#3220)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-07-05 23:13:56 +02:00
49cce6a968 stages/prompt: add basic file field (#3156)
add basic file field

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-05 23:09:41 +02:00
713337130b web: Update Web API Client version (#3219)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-07-05 23:08:14 +02:00
0a73e7ac9f tenants: add default_locale read only field, pre-hydrate in flows and read in autodetect as first choice
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-05 23:04:25 +02:00
3344af72c2 outposts: cleanup user handling
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-05 22:41:19 +02:00
41eb44137e internal: remove pkg/errors 2022-07-05 20:26:33 +00:00
94a9667d86 web: bump @formatjs/intl-listformat from 7.0.2 to 7.0.3 in /web (#3216) 2022-07-05 09:09:49 +02:00
8b56a7defb core: bump django-silk from 5.0.0 to 5.0.1 (#3217) 2022-07-05 09:09:36 +02:00
5a4b9b4239 core: bump goauthentik.io/api/v3 from 3.2022063.5 to 3.2022071.1 (#3218) 2022-07-05 09:09:24 +02:00
f37308461c web: bump @typescript-eslint/eslint-plugin from 5.30.4 to 5.30.5 in /web (#3213)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.30.4 to 5.30.5.
- [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.30.5/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 22:00:37 +02:00
9721098178 web: bump @sentry/browser from 7.4.1 to 7.5.0 in /web (#3215)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.4.1 to 7.5.0.
- [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.4.1...7.5.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 22:00:24 +02:00
0ca5e67dad web: bump rapidoc from 9.3.2 to 9.3.3 in /web (#3212)
Bumps [rapidoc](https://github.com/rapi-doc/RapiDoc) from 9.3.2 to 9.3.3.
- [Release notes](https://github.com/rapi-doc/RapiDoc/releases)
- [Commits](https://github.com/rapi-doc/RapiDoc/compare/v9.3.2...v9.3.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 22:00:11 +02:00
da94564d5e web: bump @jackfranklin/rollup-plugin-markdown from 0.3.0 to 0.4.0 in /web (#3209)
web: bump @jackfranklin/rollup-plugin-markdown in /web

Bumps @jackfranklin/rollup-plugin-markdown from 0.3.0 to 0.4.0.

---
updated-dependencies:
- dependency-name: "@jackfranklin/rollup-plugin-markdown"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 21:59:57 +02:00
1f33237659 web: bump @typescript-eslint/parser from 5.30.4 to 5.30.5 in /web (#3214)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.30.4 to 5.30.5.
- [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.30.5/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 21:59:27 +02:00
62e5979c13 web: bump @sentry/tracing from 7.4.1 to 7.5.0 in /web (#3208)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.4.1 to 7.5.0.
- [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.4.1...7.5.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 21:59:10 +02:00
8a1e18e087 website: bump rapidoc from 9.3.2 to 9.3.3 in /website (#3210)
Bumps [rapidoc](https://github.com/rapi-doc/RapiDoc) from 9.3.2 to 9.3.3.
- [Release notes](https://github.com/rapi-doc/RapiDoc/releases)
- [Commits](https://github.com/rapi-doc/RapiDoc/compare/v9.3.2...v9.3.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 21:58:19 +02:00
a951daddce web: Update Web API Client version (#3211)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-07-04 21:58:06 +02:00
690f6d444a Merge branch 'version-2022.7' 2022-07-04 21:26:47 +02:00
b733930745 website/docs: add 2022.7 to sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-04 21:14:49 +02:00
f316a3000b release: 2022.7.1 2022-07-04 21:10:20 +02:00
ddae9dc6e1 Revert "web: manage version via package.json"
This reverts commit 022401b60e.

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

# Conflicts:
#	web/src/api/Sentry.ts
#	web/src/api/Users.ts
2022-07-04 21:10:16 +02:00
0348d6558a web: Update Web API Client version (#3207)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-07-04 20:15:28 +02:00
6a497b32f6 core: use Exception for fallback case in flow_manager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-04 20:05:03 +02:00
47acc0ea90 core: bump django from 4.0.5 to 4.0.6 (#3206)
Bumps [django](https://github.com/django/django) from 4.0.5 to 4.0.6.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.0.5...4.0.6)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 12:47:58 +00:00
967c952a4a web: bump @typescript-eslint/eslint-plugin from 5.30.0 to 5.30.4 in /web (#3200)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.30.0 to 5.30.4.
- [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.30.4/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:40:16 +02:00
b648d159dd website: bump clsx from 1.1.1 to 1.2.0 in /website (#3199)
Bumps [clsx](https://github.com/lukeed/clsx) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/lukeed/clsx/releases)
- [Commits](https://github.com/lukeed/clsx/compare/v1.1.1...v1.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:39:14 +02:00
aecd9387d9 web: bump @typescript-eslint/parser from 5.30.0 to 5.30.4 in /web (#3201)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.30.0 to 5.30.4.
- [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.30.4/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:38:51 +02:00
6e8a5e1426 web: bump eslint from 8.18.0 to 8.19.0 in /web (#3202)
Bumps [eslint](https://github.com/eslint/eslint) from 8.18.0 to 8.19.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.18.0...v8.19.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:38:38 +02:00
607899be56 core: bump goauthentik.io/api/v3 from 3.2022063.4 to 3.2022063.5 (#3204)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022063.4 to 3.2022063.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022063.4...v3.2022063.5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:38:30 +02:00
5a92a8639a core: bump lxml from 4.9.0 to 4.9.1 (#3203)
Bumps [lxml](https://github.com/lxml/lxml) from 4.9.0 to 4.9.1.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.9.0...lxml-4.9.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:38:14 +02:00
4cd629b5fc core: handle FlowNonApplicableException correctly in source flow_manager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-03 22:03:03 +02:00
6020736430 website/docs: update 2022.7
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-03 21:39:59 +02:00
14a4047bdd flows: show messages from ak_message when flow is denied
fallback to same generic message

closes #3197

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-03 21:36:13 +02:00
23c1e22a04 web/elements: auto-switch themes for codemirror
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-02 23:07:09 +02:00
2a2ae4bc4f web/admin: default to users path in sidebar link
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-02 22:53:16 +02:00
5f4812e1d0 website/docs: fix Minor typo (#3196)
Signed-off-by: Stratos Gerakakis <stratos@gerakakis.net>
2022-07-02 19:48:53 +02:00
3ab475d916 website/docs: add snippet to skip authenticated flow
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-02 18:41:16 +02:00
453d64eea5 web: Update Web API Client version (#3195) 2022-07-02 17:44:59 +02:00
17d33f4b19 flows: denied action (#3194) 2022-07-02 17:37:57 +02:00
c39a5933e1 core: create FlowToken instead of regular token for generated recovery links (#3193)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2749
2022-07-02 14:17:41 +02:00
a9636b5727 website/docs: fix configuration item headers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-02 13:01:40 +02:00
5e3f44dd87 flows: add shortcut to redirect current flow (#3192) 2022-07-01 23:19:41 +02:00
1c64616ebd sources/ldap: add configuration for LDAP Source ciphers
closes #3110

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 19:53:49 +02:00
23273f53cc providers/oauth2: if no scopes are sent in authorize request, select all configured scopes
closes #3112

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 19:45:26 +02:00
d11ce0a86e providers/proxy: set default scopes based on managed attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 18:26:49 +02:00
766ceda57a core: re-create anonymous user when repairing permissions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 17:20:06 +02:00
eb633c607e internal: fix nil pointer reference
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 17:02:53 +02:00
c72d56d02d web: use full import paths for dynamic imports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 16:56:23 +02:00
e758c434ea web: ignore module load errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 16:49:37 +02:00
90e3ae9457 *: define prometheus metrics in apps to prevent re-import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 16:49:24 +02:00
0e825ffcfd root: add vscode tasks
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 16:10:08 +02:00
8a19c71f62 web: bump @sentry/browser from 7.3.1 to 7.4.1 in /web (#3191)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.3.1 to 7.4.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.3.1...7.4.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-01 16:04:03 +02:00
5a7eff041a web: bump @sentry/tracing from 7.3.1 to 7.4.1 in /web (#3190)
* ci: add custom dependabot commit messages

* web: bump @sentry/tracing from 7.3.1 to 7.4.1 in /web

Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.3.1 to 7.4.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.3.1...7.4.1)

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

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

Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-01 16:02:23 +02:00
552459834a ci: add custom dependabot commit messages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 15:50:37 +02:00
cc6325bf6a build(deps): bump codemirror from 5.65.6 to 6.0.1 in /web (#3188)
* build(deps): bump codemirror from 5.65.6 to 6.0.1 in /web

Bumps [codemirror](https://github.com/codemirror/basic-setup) from 5.65.6 to 6.0.1.
- [Release notes](https://github.com/codemirror/basic-setup/releases)
- [Changelog](https://github.com/codemirror/basic-setup/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/basic-setup/commits/6.0.1)

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

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

* upgrade

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 15:46:27 +02:00
9597ea9e1f build(deps): bump @patternfly/patternfly from 4.196.7 to 4.202.1 in /web (#3187)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-07-01 09:42:52 +02:00
69b5670659 build(deps): bump goauthentik.io/api/v3 from 3.2022063.3 to 3.2022063.4 (#3189) 2022-07-01 09:20:39 +02:00
56fd436e5d web: fix redirect when accessing authentik URLs authenticated
closes #3174

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-30 23:04:39 +02:00
b7558ae28c web: Update Web API Client version (#3186)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-30 22:26:52 +02:00
ea60c389be providers/saml: include SSO Binding URLs in Provider API
closes #3179

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-30 22:18:21 +02:00
f6042f29f6 website/docs: add notice to use in-cluster service for nginx forward auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-30 21:33:47 +02:00
983882f5a0 providers/oauth2: ensure refresh tokens are URL safe
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3185
2022-06-30 12:43:08 +02:00
a6d3fd92df web/elements: fix ak-wizard-page-form not setting valid
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-30 12:37:10 +02:00
96f39904b8 build(deps): bump lit from 2.2.6 to 2.2.7 in /web (#3181)
Bumps [lit](https://github.com/lit/lit/tree/HEAD/packages/lit) from 2.2.6 to 2.2.7.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/lit/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit@2.2.7/packages/lit)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 10:08:29 +02:00
ee347aa7ef build(deps-dev): bump pylint from 2.14.3 to 2.14.4 (#3182)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.14.3 to 2.14.4.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.14.3...v2.14.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 10:08:13 +02:00
6437334e67 build(deps): bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#3183)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.5 to 1.8.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.5...v1.8.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-30 10:05:46 +02:00
2f57d7f427 web/admin: fix wizard type selection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-29 21:45:38 +02:00
sev
db07f564aa website/docs: expand nginx reverse-proxy setup (#3079)
* website/docs: expand nginx reverse-proxy setup

* website/docs: simplify reverse-proxy config

Combine location blocks and remove documentstion links, they were not doing much.
2022-06-29 15:02:59 +00:00
d1479a1b16 web: improve detection for locales
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3163
2022-06-29 11:15:44 +02:00
4d80e207da build(deps): bump @sentry/browser from 7.3.0 to 7.3.1 in /web (#3176) 2022-06-29 09:20:48 +02:00
e7be7ac9b4 build(deps): bump @sentry/tracing from 7.3.0 to 7.3.1 in /web (#3177) 2022-06-29 09:11:37 +02:00
e0954c0f89 build(deps): bump ua-parser from 0.10.0 to 0.15.0 (#3178) 2022-06-29 09:11:22 +02:00
7ae061909c build(deps): bump @babel/preset-env from 7.18.2 to 7.18.6 in /web (#3170)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.18.2 to 7.18.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.6/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:29:19 +02:00
45a806f46b build(deps): bump @babel/plugin-proposal-decorators from 7.18.2 to 7.18.6 in /web (#3166)
build(deps): bump @babel/plugin-proposal-decorators in /web

Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-decorators) from 7.18.2 to 7.18.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.6/packages/babel-plugin-proposal-decorators)

---
updated-dependencies:
- dependency-name: "@babel/plugin-proposal-decorators"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:28:46 +02:00
feb6b07657 build(deps): bump @babel/plugin-transform-runtime from 7.18.5 to 7.18.6 in /web (#3169)
build(deps): bump @babel/plugin-transform-runtime in /web

Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.18.5 to 7.18.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.6/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:28:05 +02:00
1d98582d29 build(deps): bump @babel/preset-typescript from 7.17.12 to 7.18.6 in /web (#3168)
build(deps): bump @babel/preset-typescript in /web

Bumps [@babel/preset-typescript](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-typescript) from 7.17.12 to 7.18.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.6/packages/babel-preset-typescript)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:27:57 +02:00
06663edba2 build(deps): bump @babel/core from 7.18.5 to 7.18.6 in /web (#3165)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.5 to 7.18.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.6/packages/babel-core)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:27:47 +02:00
de0d1dc94d build(deps): bump @typescript-eslint/parser from 5.29.0 to 5.30.0 in /web (#3167)
build(deps): bump @typescript-eslint/parser in /web

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.29.0 to 5.30.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.30.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>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-28 09:27:37 +02:00
1652ea25e4 build(deps): bump @typescript-eslint/eslint-plugin from 5.29.0 to 5.30.0 in /web (#3164) 2022-06-28 09:00:22 +02:00
d794e3055c build(deps-dev): bump django-silk from 4.4.0 to 5.0.0 (#3171) 2022-06-28 08:58:47 +02:00
a92c68ac85 build(deps-dev): bump black from 22.3.0 to 22.6.0 (#3172) 2022-06-28 08:58:26 +02:00
dd41789230 build(deps): bump uvicorn from 0.18.1 to 0.18.2 (#3173) 2022-06-28 08:57:35 +02:00
022401b60e web: manage version via package.json
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-27 21:31:54 +02:00
ef218ff1ff website/docs: update 2022.7 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-27 21:12:36 +02:00
f933bf2f40 build(deps): bump @sentry/browser from 7.2.0 to 7.3.0 in /web (#3157) 2022-06-27 09:02:32 +02:00
4fc761adea build(deps): bump @rollup/plugin-commonjs from 22.0.0 to 22.0.1 in /web (#3159) 2022-06-27 09:01:52 +02:00
d11c214d32 build(deps): bump goauthentik.io/api/v3 from 3.2022063.1 to 3.2022063.3 (#3162) 2022-06-27 09:01:36 +02:00
ffbbe5ca5f build(deps): bump @sentry/tracing from 7.2.0 to 7.3.0 in /web (#3158) 2022-06-27 09:01:03 +02:00
8582091219 build(deps): bump channels from 3.0.4 to 3.0.5 (#3160) 2022-06-27 09:00:37 +02:00
28c8eb3ee6 build(deps-dev): bump importlib-metadata from 4.11.4 to 4.12.0 (#3161) 2022-06-27 09:00:26 +02:00
3a00a5ac3d website/docs: cleanup vikunja docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-26 18:29:17 +02:00
20035e0f1b ci: only hash web source
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-26 18:18:36 +02:00
67021b0e7c web: Update Web API Client version (#3155)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-26 17:55:46 +02:00
c5a2831665 api: add basic jwt support with required scope (#2624)
* api: add basic jwt support with required scope

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

* api: only set auth_via when actually authenticating via token

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

* save consented permissions in user consent, re-prompt when new permissions are required

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

* update locale

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

* translate special scope map

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

* more api auth tests

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

* add docs

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

* build web api in e2e tests

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

* link generated client instead of copying

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-26 17:51:15 +02:00
768f073e49 web: Update Web API Client version (#3152)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-26 00:51:38 +02:00
504338ea66 web/admin: application wizard (part 1) (#2745)
* initial

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

* remove log

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

* start oauth

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

* use form for all type wizard pages

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

* more oauth

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

* basic wizard actions

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

* make resets work

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

* add hint in provider wizard

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

* render correct icon in empty state in table page

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

* improve empty state

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

* more

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

* add more pages

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

* fix

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

* add group PK to service account creation response

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

* use wizard-level isValid prop

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

* re-add old buttons

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-26 00:46:40 +02:00
a8c04f96d2 web: use absolute imports with path rewrite instead of relative imports (#3149) 2022-06-25 17:44:17 +02:00
340faf5341 lifecycle: Update postgres healthcheck for compose with user information (#3143)
Update postgres healthcheck with user information
2022-06-24 12:02:05 +02:00
a76c39aff9 build(deps-dev): bump selenium from 4.2.0 to 4.3.0 (#3144) 2022-06-24 08:19:42 +02:00
bb728a53cc build(deps): bump uvicorn from 0.17.6 to 0.18.1 (#3145) 2022-06-24 08:19:31 +02:00
5c28a7dd44 build(deps): bump github.com/stretchr/testify from 1.7.4 to 1.7.5 (#3146) 2022-06-24 07:59:51 +02:00
e1efb47543 web/flows: statically import webauthn-related stages for safari issues
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-23 19:26:31 +02:00
e50a296a18 build(deps): bump @lingui/core from 3.13.3 to 3.14.0 in /web (#3136)
Bumps [@lingui/core](https://github.com/lingui/js-lingui) from 3.13.3 to 3.14.0.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.3...v3.14.0)

---
updated-dependencies:
- dependency-name: "@lingui/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:48:02 +02:00
e211265c30 build(deps): bump @lingui/cli from 3.13.3 to 3.14.0 in /web (#3137)
Bumps [@lingui/cli](https://github.com/lingui/js-lingui) from 3.13.3 to 3.14.0.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.3...v3.14.0)

---
updated-dependencies:
- dependency-name: "@lingui/cli"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:46:23 +02:00
1f143a24db build(deps): bump @lingui/macro from 3.13.3 to 3.14.0 in /web (#3138)
Bumps [@lingui/macro](https://github.com/lingui/js-lingui) from 3.13.3 to 3.14.0.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.3...v3.14.0)

---
updated-dependencies:
- dependency-name: "@lingui/macro"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:46:07 +02:00
48f490b810 build(deps): bump sentry-sdk from 1.5.12 to 1.6.0 (#3140)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.5.12 to 1.6.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.5.12...1.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:45:39 +02:00
aed382de0c build(deps): bump pycryptodome from 3.14.1 to 3.15.0 (#3141)
Bumps [pycryptodome](https://github.com/Legrandin/pycryptodome) from 3.14.1 to 3.15.0.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/compare/v3.14.1...v3.15.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:45:27 +02:00
8ecf40a58b build(deps): bump kubernetes from 23.6.0 to 24.2.0 (#3142)
Bumps [kubernetes](https://github.com/kubernetes-client/python) from 23.6.0 to 24.2.0.
- [Release notes](https://github.com/kubernetes-client/python/releases)
- [Changelog](https://github.com/kubernetes-client/python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes-client/python/compare/v23.6.0...v24.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:45:10 +02:00
aca3c75e17 build(deps): bump @lingui/detect-locale from 3.13.3 to 3.14.0 in /web (#3139)
Bumps [@lingui/detect-locale](https://github.com/lingui/js-lingui) from 3.13.3 to 3.14.0.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.3...v3.14.0)

---
updated-dependencies:
- dependency-name: "@lingui/detect-locale"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-23 09:44:51 +02:00
f28509608b core: mark session as modified instead of saving it directly to bump expiry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-22 08:48:14 +02:00
ff6c508de7 build(deps): bump geoip2 from 4.5.0 to 4.6.0 (#3133)
Bumps [geoip2](https://github.com/maxmind/GeoIP2-python) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/maxmind/GeoIP2-python/releases)
- [Changelog](https://github.com/maxmind/GeoIP2-python/blob/v4.6.0/HISTORY.rst)
- [Commits](https://github.com/maxmind/GeoIP2-python/compare/v4.5.0...v4.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-22 08:32:01 +02:00
7319ea2dcf ci: add version family (#3059)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-21 23:31:27 +02:00
6a4efaecb0 website/docs: start troubleshooting page for forward auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-21 22:26:17 +02:00
29b0eae43f web/admin: only pre-select oauth2 provider key if creating a new instance
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-21 19:51:24 +02:00
9f3e742fb1 build(deps): bump github.com/stretchr/testify from 1.7.2 to 1.7.4 (#3132) 2022-06-21 06:51:55 +02:00
c8e09fea33 build(deps): bump @typescript-eslint/eslint-plugin from 5.28.0 to 5.29.0 in /web (#3128) 2022-06-21 06:45:13 +02:00
437e932471 build(deps): bump goauthentik.io/api/v3 from 3.2022062.3 to 3.2022063.1 (#3131) 2022-06-21 06:44:54 +02:00
ce07d71d23 build(deps): bump codemirror from 5.65.5 to 5.65.6 in /web (#3126) 2022-06-21 06:44:31 +02:00
9815c591e0 build(deps): bump rollup from 2.75.6 to 2.75.7 in /web (#3127) 2022-06-21 06:42:43 +02:00
db7a3ab630 build(deps): bump @typescript-eslint/parser from 5.28.0 to 5.29.0 in /web (#3129) 2022-06-21 06:42:27 +02:00
3fa772c32e build(deps-dev): bump django-silk from 4.3.0 to 4.4.0 (#3130) 2022-06-21 06:42:10 +02:00
6c9dc7a15b providers/oauth2: fix OAuth form_post response mode for code response_type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3113
2022-06-20 21:52:36 +02:00
ece0429ea8 internal: failback with self-signed cert if cert for tenant fails to load
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-20 21:26:34 +02:00
d56ddb16b1 lifecycle: fix confusing success messages in startup healthiness check
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-20 21:26:21 +02:00
b6267fdf28 *: add versioned user agent to sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-20 11:54:10 +02:00
1f190a9255 web: Update Web API Client version (#3124)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-20 10:38:37 +02:00
1f0fc0a6a2 Merge branch 'version-2022.6' 2022-06-20 10:19:25 +02:00
3ba678851e build(deps): bump @sentry/browser from 7.1.1 to 7.2.0 in /web (#3118)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.1.1 to 7.2.0.
- [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.1.1...7.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:18:52 +02:00
0869ef3d0d build(deps): bump @sentry/tracing from 7.1.1 to 7.2.0 in /web (#3119)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.1.1 to 7.2.0.
- [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.1.1...7.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:11:28 +02:00
91100ce1e2 build(deps): bump eslint from 8.17.0 to 8.18.0 in /web (#3120)
Bumps [eslint](https://github.com/eslint/eslint) from 8.17.0 to 8.18.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.17.0...v8.18.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:06:34 +02:00
a65ce47736 build(deps): bump typescript from 4.7.3 to 4.7.4 in /web (#3121)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.7.3 to 4.7.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.7.3...v4.7.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:05:53 +02:00
def17bbc1e build(deps-dev): bump pylint from 2.14.2 to 2.14.3 (#3122)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.14.2 to 2.14.3.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.14.2...v2.14.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:05:43 +02:00
eb7da8f414 build(deps): bump django-filter from 21.1 to 22.1 (#3123)
Bumps [django-filter](https://github.com/carltongibson/django-filter) from 21.1 to 22.1.
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/21.1...22.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 10:05:26 +02:00
9201fc1834 release: 2022.6.3 2022-06-19 22:01:06 +02:00
5385feb428 website/docs: add 2022.6.3 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:41:36 +02:00
c6f29d9eb4 website/docs: add 2022.6.3 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:41:22 +02:00
db557401aa web/admin: lint bound group under policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:28 +02:00
c824af5bc3 web/elements: add spinner when loading dynamic routes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:22 +02:00
1faba11a57 providers/oauth2: add test to ensure capitalised redirect_uri isn't changed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3114
2022-06-19 21:37:20 +02:00
9p4
f0c72e8536 providers/oauth2: dont lowercase URL for token requests (#3114)
this was a leftover from before the migration regex checking for redirect URIs

closes #3076 and #3083
2022-06-19 21:37:17 +02:00
91f91b08e5 core: fix migrations when creating bootstrap token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:14 +02:00
8faa909c32 internal: fix routing to embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:37:03 +02:00
49142fa80b internal: dont sample gunicorn proxied requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:57 +02:00
2a6fccd22a providers/proxy: only send misconfiguration event once
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:50 +02:00
1d10afa209 website/docs: add version dropdown for subdomains
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:45 +02:00
4b7c3c38cd website/docs: support levels (#3103)
* website/docs: add badges for integration level

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

* add badge for sources

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:42 +02:00
440cacbafe webiste/docs: use autogenerated pages and categories (#3102)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:39 +02:00
b33bff92ee web/flows: fix error when webauthn operations failed and user retries
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:28 +02:00
caed306346 providers/oauth2: if a redirect_uri cannot be parsed as regex, compare strict (#3070)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:19 +02:00
d0eb6af7e9 web/admin: remove invalid requirement for usernames
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:15 +02:00
ec5ed67f6c web/flows: add divider to identification stage for security key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:36:08 +02:00
59b899ddff internal: skip tracing for go healthcheck and metrics endpoints
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:48 +02:00
85784f796c root: ignore healthcheck routes in sentry tracing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:46 +02:00
4c0e19cbea web/flows: remove autofocus from password field of identifications tage
closes #2561

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:43 +02:00
b42eb9464f lifecycle: run bootstrap tasks inline when using automated install
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:33 +02:00
6559fdee15 stages/authenticator_validate: add webauthn tests (#3069)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:23 +02:00
3455bf3d27 policies: consolidate log user and application
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:35:04 +02:00
0d96e68c1e core: add limit of 20 to group recursion
closes #3116

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:24:57 +02:00
29d3db5112 web/admin: lint bound group under policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-19 21:22:47 +02:00
cdf88e4477 web/elements: add spinner when loading dynamic routes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-18 13:36:18 +02:00
7caac1d0c7 providers/oauth2: add test to ensure capitalised redirect_uri isn't changed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#3114
2022-06-18 13:13:36 +02:00
9p4
45364d6553 providers/oauth2: dont lowercase URL for token requests (#3114)
this was a leftover from before the migration regex checking for redirect URIs

closes #3076 and #3083
2022-06-18 13:08:15 +02:00
2298eb124f core: fix migrations when creating bootstrap token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-17 10:10:04 +02:00
6dff1f8e5e build(deps-dev): bump prettier from 2.7.0 to 2.7.1 in /website (#3107)
Bumps [prettier](https://github.com/prettier/prettier) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.7.0...2.7.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-17 09:38:57 +02:00
a944701f3a build(deps): bump prettier from 2.7.0 to 2.7.1 in /web (#3108)
Bumps [prettier](https://github.com/prettier/prettier) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.7.0...2.7.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-17 09:38:46 +02:00
23866fe459 build(deps): bump colorama from 0.4.4 to 0.4.5 (#3109)
Bumps [colorama](https://github.com/tartley/colorama) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/tartley/colorama/releases)
- [Changelog](https://github.com/tartley/colorama/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tartley/colorama/compare/0.4.4...0.4.5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-17 09:38:37 +02:00
0a83b04419 internal: fix routing to embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-16 17:05:27 +02:00
e6ecdf8b1d website/admin: add missing description for user_write path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-16 16:59:56 +02:00
2d48fe42f4 internal: dont sample gunicorn proxied requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-16 11:32:21 +02:00
5894ccdaf2 build(deps): bump goauthentik.io/api/v3 from 3.2022062.2 to 3.2022062.3 (#3106)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022062.2 to 3.2022062.3.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022062.2...v3.2022062.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-16 10:36:50 +02:00
79bec6f6b2 providers/proxy: only send misconfiguration event once
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-16 10:32:34 +02:00
9610f96c11 build(deps-dev): bump pylint from 2.14.1 to 2.14.2 (#3105) 2022-06-16 09:58:29 +02:00
36a326cd81 website/docs: add version dropdown for subdomains
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 21:43:44 +02:00
c0c222a0b8 website/docs: support levels (#3103)
* website/docs: add badges for integration level

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

* add badge for sources

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 21:31:34 +02:00
e17f7020e6 webiste/docs: use autogenerated pages and categories (#3102)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 20:56:27 +02:00
6d9579d3e6 web/elements: rework treeview parents
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 20:06:01 +02:00
9f15ee8cb8 web/elements: fix child items not having a parent set causing wrong paths
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 14:25:01 +02:00
e892ed14da providers/oauth2: include source's user path in M2M created users
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 14:07:28 +02:00
093a67525a web: Update Web API Client version (#3101)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-15 12:24:22 +02:00
1c62a3db6e core: user paths (#3085)
* init

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

* add user_path_template

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

* add to sources and flow

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

* add outposts & api

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

* dark theme for treeview

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

* add search

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

* add docs and tests for validation

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

* add to user write stage

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

* add web ui

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

* web: improve error handling

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-15 12:12:26 +02:00
c4b4c7134d build(deps): bump lit from 2.2.5 to 2.2.6 in /web (#3095)
Bumps [lit](https://github.com/lit/lit/tree/HEAD/packages/lit) from 2.2.5 to 2.2.6.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/lit/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit@2.2.6/packages/lit)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 09:45:27 +02:00
82cb6d41b8 build(deps): bump prettier from 2.6.2 to 2.7.0 in /web (#3097)
Bumps [prettier](https://github.com/prettier/prettier) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.6.2...2.7.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 09:45:18 +02:00
423380d987 build(deps-dev): bump prettier from 2.6.2 to 2.7.0 in /website (#3096)
Bumps [prettier](https://github.com/prettier/prettier) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.6.2...2.7.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 09:45:11 +02:00
175d97fdcf build(deps): bump helm/kind-action from 1.2.0 to 1.3.0 (#3098)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-15 09:45:01 +02:00
5dbbf970b0 build(deps): bump @typescript-eslint/eslint-plugin from 5.27.1 to 5.28.0 in /web (#3089)
build(deps): bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.27.1 to 5.28.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.28.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>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-14 09:50:03 +02:00
1541d477af build(deps): bump @babel/plugin-transform-runtime from 7.18.2 to 7.18.5 in /web (#3088)
build(deps): bump @babel/plugin-transform-runtime in /web

Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.18.2 to 7.18.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.5/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-14 09:48:51 +02:00
d745331654 build(deps): bump @babel/core from 7.18.2 to 7.18.5 in /web (#3090)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.2 to 7.18.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.5/packages/babel-core)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-14 09:48:37 +02:00
defbdc5068 build(deps): bump @typescript-eslint/parser from 5.27.1 to 5.28.0 in /web (#3091)
build(deps): bump @typescript-eslint/parser in /web

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.27.1 to 5.28.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.28.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>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-14 09:48:28 +02:00
350f0d8365 build(deps): bump goauthentik.io/api/v3 from 3.2022062.1 to 3.2022062.2 (#3082)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022062.1 to 3.2022062.2.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022062.1...v3.2022062.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 09:35:15 +02:00
b5c93fb3e3 build(deps): bump @rollup/plugin-typescript from 8.3.2 to 8.3.3 in /web (#3081)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins/tree/HEAD/packages/typescript) from 8.3.2 to 8.3.3.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/typescript/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/typescript-v8.3.3/packages/typescript)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-13 09:35:05 +02:00
5be45ebf8e web/flows: fix error when webauthn operations failed and user retries
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-12 17:42:23 +02:00
ad8fe9fe81 website/integrations: add Sonarr Proxy Setup (#3067)
I was stuck after setting up as the guide never actually mentions you need to make the change to your reverse proxy

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-11 19:52:22 +02:00
c2f7edaa42 web: Update Web API Client version (#3078)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-11 19:51:05 +02:00
6821402fef providers/oauth2: remove deprecated verification_keys (#3071)
remove verification_keys

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-11 19:48:07 +02:00
8dbb0bd2c6 providers/oauth2: token revoke (#3077) 2022-06-11 18:49:16 +02:00
24a21c1761 web/flows: update default flow background
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 23:33:17 +02:00
0cad56ec73 providers/oauth2: if a redirect_uri cannot be parsed as regex, compare strict (#3070)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 23:32:57 +02:00
4d8021c403 web/admin: remove invalid requirement for usernames
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 22:59:59 +02:00
6573cbb16c web/flows: add divider to identification stage for security key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 22:50:40 +02:00
bdf76bb4b7 internal: skip tracing for go healthcheck and metrics endpoints
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 22:21:11 +02:00
74ce9cc6fd root: ignore healthcheck routes in sentry tracing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 20:10:27 +02:00
070a6d866e web/flows: remove autofocus from password field of identifications tage
closes #2561

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-10 20:02:48 +02:00
5e2d647a6c core: trigger bootstrap tasks in server if we're debugging
closes #3040

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-09 20:21:31 +02:00
7beebe030d lifecycle: run bootstrap tasks inline when using automated install
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-09 20:09:55 +02:00
66f4a31b4c stages/authenticator_validate: add webauthn tests (#3069)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-08 20:50:48 +02:00
beddd6a460 web: update sentry and remove rollup-plugin-sourcemaps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-08 20:32:56 +02:00
faec866581 build(deps): bump python from 3.10.4-slim-bullseye to 3.10.5-slim-bullseye (#3062)
build(deps): bump python

Bumps python from 3.10.4-slim-bullseye to 3.10.5-slim-bullseye.

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-08 09:46:58 +02:00
effed50cc1 build(deps): bump rollup from 2.75.5 to 2.75.6 in /web (#3064) 2022-06-08 09:29:15 +02:00
38ad6096ad build(deps): bump goauthentik.io/api/v3 from 3.2022061.3 to 3.2022062.1 (#3066) 2022-06-08 09:28:38 +02:00
bd53042553 web: Update Web API Client version (#3061)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-06-07 22:30:36 +02:00
039d896dee policies: consolidate log user and application
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-06-07 22:26:01 +02:00
527 changed files with 16299 additions and 8724 deletions

View File

@ -1,30 +1,18 @@
[bumpversion]
current_version = 2022.6.2
current_version = 2022.7.3
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
serialize =
{major}.{minor}.{patch}-{release}
{major}.{minor}.{patch}
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
serialize = {major}.{minor}.{patch}
message = release: {new_version}
tag_name = version/{new_version}
[bumpversion:part:release]
optional_value = stable
first_value = beta
values =
alpha
beta
stable
[bumpversion:file:pyproject.toml]
[bumpversion:file:docker-compose.yml]
[bumpversion:file:schema.yml]
[bumpversion:file:.github/workflows/release-publish.yml]
[bumpversion:file:authentik/__init__.py]
[bumpversion:file:internal/constants/constants.go]

View File

@ -17,6 +17,12 @@ outputs:
sha:
description: "sha"
value: ${{ steps.ev.outputs.sha }}
version:
description: "version"
value: ${{ steps.ev.outputs.version }}
versionFamily:
description: "versionFamily"
value: ${{ steps.ev.outputs.versionFamily }}
runs:
using: "composite"
@ -47,3 +53,11 @@ runs:
print("##[set-output name=timestamp]%s" % int(time()))
print("##[set-output name=sha]%s" % os.environ[sha])
print("##[set-output name=shouldBuild]%s" % should_build)
import configparser
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
version = parser.get("bumpversion", "current_version")
version_family = ".".join(version.split(".")[:-1])
print("##[set-output name=version]%s" % version)
print("##[set-output name=versionFamily]%s" % version_family)

108
.github/dependabot.yml vendored
View File

@ -1,50 +1,62 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: npm
directory: "/web"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: npm
directory: "/website"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: docker
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "ci:"
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "core:"
- package-ecosystem: npm
directory: "/web"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "web:"
- package-ecosystem: npm
directory: "/website"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "website:"
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "core:"
- package-ecosystem: docker
directory: "/"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
reviewers:
- "@goauthentik/core"
commit-message:
prefix: "core:"

View File

@ -106,7 +106,7 @@ jobs:
with:
domain: ${{github.repository_owner}}
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.2.0
uses: helm/kind-action@v1.3.0
- name: run integration
run: |
poetry run make test-integration
@ -133,12 +133,13 @@ jobs:
uses: actions/cache@v3
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
npm ci
make -C .. gen-client-web
npm run build
- name: run e2e
run: |
@ -166,12 +167,13 @@ jobs:
uses: actions/cache@v3
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web/
run: |
npm ci
make -C .. gen-client-web
npm run build
- name: run e2e
run: |
@ -211,10 +213,10 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
uses: ./.github/actions/docker-setup
- name: Login to Container Registry
uses: docker/login-action@v2
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
@ -231,4 +233,5 @@ jobs:
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
platforms: ${{ matrix.arch }}

View File

@ -67,8 +67,8 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
uses: ./.github/actions/docker-setup
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
- name: Login to Container Registry
@ -91,6 +91,7 @@ jobs:
file: ${{ matrix.type }}.Dockerfile
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
platforms: ${{ matrix.arch }}
build-outpost-binary:
timeout-minutes: 120
@ -110,7 +111,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -47,13 +47,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
run: |
npm ci
# lit-analyse doesn't understand path rewrites, so make it
# belive it's an actual module
cd node_modules/@goauthentik
ln -s ../../src/ web
- name: Generate API
run: make gen-client-web
- name: lit-analyse
@ -73,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'

View File

@ -5,7 +5,6 @@ on:
types: [published, created]
jobs:
# Build
build-server:
runs-on: ubuntu-latest
steps:
@ -14,6 +13,9 @@ jobs:
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
- name: Docker Login Registry
uses: docker/login-action@v2
with:
@ -30,9 +32,11 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2022.6.2,
beryju/authentik:${{ steps.ev.outputs.version }},
beryju/authentik:${{ steps.ev.outputs.versionFamily }},
beryju/authentik:latest,
ghcr.io/goauthentik/server:2022.6.2,
ghcr.io/goauthentik/server:${{ steps.ev.outputs.version }},
ghcr.io/goauthentik/server:${{ steps.ev.outputs.versionFamily }},
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
@ -53,6 +57,9 @@ jobs:
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
- name: Docker Login Registry
uses: docker/login-action@v2
with:
@ -69,9 +76,11 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-${{ matrix.type }}:2022.6.2,
beryju/authentik-${{ matrix.type }}:${{ steps.ev.outputs.version }},
beryju/authentik-${{ matrix.type }}:${{ steps.ev.outputs.versionFamily }},
beryju/authentik-${{ matrix.type }}:latest,
ghcr.io/goauthentik/${{ matrix.type }}:2022.6.2,
ghcr.io/goauthentik/${{ matrix.type }}:${{ steps.ev.outputs.version }},
ghcr.io/goauthentik/${{ matrix.type }}:${{ steps.ev.outputs.versionFamily }},
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
@ -91,7 +100,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
cache: 'npm'
@ -138,6 +147,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
- name: Get static files from docker image
run: |
docker pull ghcr.io/goauthentik/server:latest
@ -152,7 +164,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2022.6.2
version: authentik@${{ steps.ev.outputs.version }}
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v3
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3.3.0
- uses: actions/setup-node@v3.4.1
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'

1
.gitignore vendored
View File

@ -193,7 +193,6 @@ pip-selfcheck.json
# End of https://www.gitignore.io/api/python,django
/static/
local.env.yml
.vscode/
# Selenium Screenshots
selenium_screenshots/

View File

@ -22,5 +22,9 @@
"python.formatting.provider": "black",
"files.associations": {
"*.akflow": "json"
}
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./web/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

86
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,86 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "authentik[core]: format & test",
"command": "poetry",
"args": [
"run",
"make"
],
"group": "build",
},
{
"label": "authentik[core]: run",
"command": "poetry",
"args": [
"run",
"make",
"run",
],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
},
},
{
"label": "authentik[web]: format",
"command": "make",
"args": ["web"],
"group": "build",
},
{
"label": "authentik[web]: watch",
"command": "make",
"args": ["web-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
},
},
{
"label": "authentik: install",
"command": "make",
"args": ["install"],
"group": "build",
},
{
"label": "authentik: i18n-extract",
"command": "poetry",
"args": [
"run",
"make",
"i18n-extract"
],
"group": "build",
},
{
"label": "authentik[website]: format",
"command": "make",
"args": ["website"],
"group": "build",
},
{
"label": "authentik[website]: watch",
"command": "make",
"args": ["website-watch"],
"group": "build",
"presentation": {
"panel": "dedicated",
"group": "running"
},
},
{
"label": "authentik[api]: generate",
"command": "poetry",
"args": [
"run",
"make",
"gen"
],
"group": "build"
},
]
}

View File

@ -18,7 +18,7 @@ WORKDIR /work/web
RUN npm ci && npm run build
# Stage 3: Poetry to requirements.txt export
FROM docker.io/python:3.10.4-slim-bullseye AS poetry-locker
FROM docker.io/python:3.10.5-slim-bullseye AS poetry-locker
WORKDIR /work
COPY ./pyproject.toml /work
@ -29,7 +29,7 @@ RUN pip install --no-cache-dir poetry && \
poetry export -f requirements.txt --dev --output requirements-dev.txt
# Stage 4: Build go proxy
FROM docker.io/golang:1.18.3-bullseye AS builder
FROM docker.io/golang:1.18.4-bullseye AS builder
WORKDIR /work
@ -45,7 +45,7 @@ COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
FROM docker.io/python:3.10.4-slim-bullseye
FROM docker.io/python:3.10.5-slim-bullseye
LABEL org.opencontainers.image.url https://goauthentik.io
LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info.

View File

@ -45,8 +45,8 @@ lint-fix:
website/developer-docs
lint:
bandit -r authentik tests lifecycle -x node_modules
pylint authentik tests lifecycle
bandit -r authentik tests lifecycle -x node_modules
golangci-lint run -v
i18n-extract: i18n-extract-core web-extract
@ -106,12 +106,15 @@ run:
web-build: web-install
cd web && npm run build
web: web-lint-fix web-lint web-extract
web: web-lint-fix web-lint
web-install:
cd web && npm ci
web-watch:
rm -rf web/dist/
mkdir web/dist/
touch web/dist/.gitkeep
cd web && npm run watch
web-lint-fix:
@ -166,8 +169,3 @@ ci-pending-migrations: ci--meta-debug
install: web-install website-install
poetry install
a: install
tmux \
new-session 'make run' \; \
split-window 'make web-watch'

View File

@ -6,8 +6,9 @@
| Version | Supported |
| ---------- | ------------------ |
| 2022.4.x | :white_check_mark: |
| 2022.5.x | :white_check_mark: |
| 2022.6.x | :white_check_mark: |
| 2022.7.x | :white_check_mark: |
## Reporting a Vulnerability

View File

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

View File

@ -1,7 +1,6 @@
"""authentik administration overview"""
from django.conf import settings
from drf_spectacular.utils import extend_schema, inline_serializer
from prometheus_client import Gauge
from rest_framework.fields import IntegerField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -10,8 +9,6 @@ from rest_framework.views import APIView
from authentik.root.celery import CELERY_APP
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
class WorkerView(APIView):
"""Get currently connected worker count."""

View File

@ -2,6 +2,10 @@
from importlib import import_module
from django.apps import AppConfig
from prometheus_client import Gauge, Info
PROM_INFO = Info("authentik_version", "Currently running authentik version")
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
class AuthentikAdminConfig(AppConfig):

View File

@ -2,7 +2,7 @@
from django.dispatch import receiver
from authentik.admin.api.tasks import TaskInfo
from authentik.admin.api.workers import GAUGE_WORKERS
from authentik.admin.apps import GAUGE_WORKERS
from authentik.root.celery import CELERY_APP
from authentik.root.monitoring import monitoring_set

View File

@ -4,11 +4,11 @@ import re
from django.core.cache import cache
from django.core.validators import URLValidator
from packaging.version import parse
from prometheus_client import Info
from requests import RequestException
from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash
from authentik.admin.apps import PROM_INFO
from authentik.events.models import Event, EventAction, Notification
from authentik.events.monitored_tasks import (
MonitoredTask,
@ -25,7 +25,6 @@ VERSION_CACHE_KEY = "authentik_latest_version"
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
# Chop of the first ^ because we want to search the entire string
URL_FINDER = URLValidator.regex.pattern[1:]
PROM_INFO = Info("authentik_version", "Currently running authentik version")
LOCAL_VERSION = parse(__version__)

View File

@ -10,6 +10,8 @@ from structlog.stdlib import get_logger
from authentik.core.middleware import KEY_AUTH_VIA, LOCAL
from authentik.core.models import Token, TokenIntents, User
from authentik.outposts.models import Outpost
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import RefreshToken
LOGGER = get_logger()
@ -24,7 +26,7 @@ def validate_auth(header: bytes) -> str:
if auth_type.lower() != "bearer":
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
raise AuthenticationFailed("Unsupported authentication type")
if auth_credentials == "": # nosec
if auth_credentials == "": # nosec # noqa
raise AuthenticationFailed("Malformed header")
return auth_credentials
@ -34,14 +36,30 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
auth_credentials = validate_auth(raw_header)
if not auth_credentials:
return None
if not hasattr(LOCAL, "authentik"):
LOCAL.authentik = {}
# first, check traditional tokens
token = Token.filter_not_expired(key=auth_credentials, intent=TokenIntents.INTENT_API).first()
if hasattr(LOCAL, "authentik"):
key_token = Token.filter_not_expired(
key=auth_credentials, intent=TokenIntents.INTENT_API
).first()
if key_token:
LOCAL.authentik[KEY_AUTH_VIA] = "api_token"
if token:
return token.user
return key_token.user
# then try to auth via JWT
jwt_token = RefreshToken.filter_not_expired(
refresh_token=auth_credentials, _scope__icontains=SCOPE_AUTHENTIK_API
).first()
if jwt_token:
# Double-check scopes, since they are saved in a single string
# we want to check the parsed version too
if SCOPE_AUTHENTIK_API not in jwt_token.scope:
raise AuthenticationFailed("Token invalid/expired")
LOCAL.authentik[KEY_AUTH_VIA] = "jwt"
return jwt_token.user
# then try to auth via secret key (for embedded outpost/etc)
user = token_secret_key(auth_credentials)
if user:
LOCAL.authentik[KEY_AUTH_VIA] = "secret_key"
return user
raise AuthenticationFailed("Token invalid/expired")
@ -56,8 +74,6 @@ def token_secret_key(value: str) -> Optional[User]:
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts:
return None
if hasattr(LOCAL, "authentik"):
LOCAL.authentik[KEY_AUTH_VIA] = "secret_key"
outpost = outposts.first()
return outpost.user

View File

@ -8,28 +8,37 @@ from rest_framework.exceptions import AuthenticationFailed
from authentik.api.authentication import bearer_auth
from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents
from authentik.core.tests.utils import create_test_flow
from authentik.lib.generators import generate_id
from authentik.outposts.managed import OutpostManager
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
class TestAPIAuth(TestCase):
"""Test API Authentication"""
def test_valid_bearer(self):
"""Test valid token"""
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=get_anonymous_user())
self.assertEqual(bearer_auth(f"Bearer {token.key}".encode()), token.user)
def test_invalid_type(self):
"""Test invalid type"""
with self.assertRaises(AuthenticationFailed):
bearer_auth("foo bar".encode())
def test_invalid_empty(self):
"""Test invalid type"""
self.assertIsNone(bearer_auth("Bearer ".encode()))
self.assertIsNone(bearer_auth("".encode()))
def test_invalid_no_token(self):
"""Test invalid with no token"""
with self.assertRaises(AuthenticationFailed):
auth = b64encode(":abc".encode()).decode()
self.assertIsNone(bearer_auth(f"Basic :{auth}".encode()))
def test_bearer_valid(self):
"""Test valid token"""
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=get_anonymous_user())
self.assertEqual(bearer_auth(f"Bearer {token.key}".encode()), token.user)
def test_managed_outpost(self):
"""Test managed outpost"""
with self.assertRaises(AuthenticationFailed):
@ -38,3 +47,30 @@ class TestAPIAuth(TestCase):
OutpostManager().run()
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
self.assertEqual(user.attributes[USER_ATTRIBUTE_SA], True)
def test_jwt_valid(self):
"""Test valid JWT"""
provider = OAuth2Provider.objects.create(
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
)
refresh = RefreshToken.objects.create(
user=get_anonymous_user(),
provider=provider,
refresh_token=generate_id(),
_scope=SCOPE_AUTHENTIK_API,
)
self.assertEqual(bearer_auth(f"Bearer {refresh.refresh_token}".encode()), refresh.user)
def test_jwt_missing_scope(self):
"""Test valid JWT"""
provider = OAuth2Provider.objects.create(
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
)
refresh = RefreshToken.objects.create(
user=get_anonymous_user(),
provider=provider,
refresh_token=generate_id(),
_scope="",
)
with self.assertRaises(AuthenticationFailed):
self.assertEqual(bearer_auth(f"Bearer {refresh.refresh_token}".encode()), refresh.user)

View File

@ -74,7 +74,7 @@ class ConfigView(APIView):
config = ConfigSerializer(
{
"error_reporting": {
"enabled": CONFIG.y("error_reporting.enabled") and not settings.DEBUG,
"enabled": CONFIG.y("error_reporting.enabled"),
"environment": CONFIG.y("error_reporting.environment"),
"send_pii": CONFIG.y("error_reporting.send_pii"),
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)),

View File

@ -89,6 +89,14 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
"meta_publisher",
"group",
]
filterset_fields = [
"name",
"slug",
"meta_launch_url",
"meta_description",
"meta_publisher",
"group",
]
lookup_field = "slug"
filterset_fields = ["name", "slug"]
ordering = ["name"]

View File

@ -53,6 +53,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
"policy_engine_mode",
"user_matching_mode",
"managed",
"user_path_template",
]

View File

@ -24,7 +24,13 @@ from drf_spectacular.utils import (
)
from guardian.shortcuts import get_anonymous_user, get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
from rest_framework.fields import (
CharField,
IntegerField,
JSONField,
ListField,
SerializerMethodField,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -50,12 +56,16 @@ from authentik.core.middleware import (
from authentik.core.models import (
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
USER_PATH_SERVICE_ACCOUNT,
Group,
Token,
TokenIntents,
User,
)
from authentik.events.models import EventAction
from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -77,6 +87,15 @@ class UserSerializer(ModelSerializer):
uid = CharField(read_only=True)
username = CharField(max_length=150)
def validate_path(self, path: str) -> str:
"""Validate path"""
if path[:1] == "/" or path[-1] == "/":
raise ValidationError(_("No leading or trailing slashes allowed."))
for segment in path.split("/"):
if segment == "":
raise ValidationError(_("No empty segments in user path allowed."))
return path
class Meta:
model = User
@ -93,6 +112,7 @@ class UserSerializer(ModelSerializer):
"avatar",
"attributes",
"uid",
"path",
]
extra_kwargs = {
"name": {"allow_blank": True},
@ -208,6 +228,11 @@ class UsersFilter(FilterSet):
is_superuser = BooleanFilter(field_name="ak_groups", lookup_expr="is_superuser")
uuid = CharFilter(field_name="uuid")
path = CharFilter(
field_name="path",
)
path_startswith = CharFilter(field_name="path", lookup_expr="startswith")
groups_by_name = ModelMultipleChoiceFilter(
field_name="ak_groups__name",
to_field_name="name",
@ -272,12 +297,23 @@ class UserViewSet(UsedByMixin, ModelViewSet):
LOGGER.debug("No recovery flow set")
return None, None
user: User = self.get_object()
token, __ = Token.objects.get_or_create(
identifier=f"{user.uid}-password-reset",
user=user,
intent=TokenIntents.INTENT_RECOVERY,
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
plan = planner.plan(
self.request._request,
{
PLAN_CONTEXT_PENDING_USER: user,
},
)
querystring = urlencode({"token": token.key})
token, __ = FlowToken.objects.update_or_create(
identifier=f"{user.uid}-password-reset",
defaults={
"user": user,
"flow": flow,
"_plan": FlowToken.pickle(plan),
},
)
querystring = urlencode({QS_KEY_TOKEN: token.key})
link = self.request.build_absolute_uri(
reverse_lazy("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
+ f"?{querystring}"
@ -299,6 +335,9 @@ class UserViewSet(UsedByMixin, ModelViewSet):
{
"username": CharField(required=True),
"token": CharField(required=True),
"user_uid": CharField(required=True),
"user_pk": IntegerField(required=True),
"group_pk": CharField(required=False),
},
)
},
@ -314,19 +353,27 @@ class UserViewSet(UsedByMixin, ModelViewSet):
username=username,
name=username,
attributes={USER_ATTRIBUTE_SA: True, USER_ATTRIBUTE_TOKEN_EXPIRING: False},
path=USER_PATH_SERVICE_ACCOUNT,
)
response = {
"username": user.username,
"user_uid": user.uid,
"user_pk": user.pk,
}
if create_group and self.request.user.has_perm("authentik_core.add_group"):
group = Group.objects.create(
name=username,
)
group.users.add(user)
response["group_pk"] = str(group.pk)
token = Token.objects.create(
identifier=slugify(f"service-account-{username}-password"),
intent=TokenIntents.INTENT_APP_PASSWORD,
user=user,
expires=now() + timedelta(days=360),
)
return Response({"username": user.username, "token": token.key})
response["token"] = token.key
return Response(response)
except (IntegrityError) as exc:
return Response(data={"non_field_errors": [str(exc)]}, status=400)
@ -344,7 +391,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
instance=request._request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER],
context=context,
).data
self.request.session.save()
self.request.session.modified = True
return Response(serializer.initial_data)
@permission_required("authentik_core.reset_user_password")
@ -464,3 +511,32 @@ class UserViewSet(UsedByMixin, ModelViewSet):
if self.request.user.has_perm("authentik_core.view_user"):
return self._filter_queryset_for_list(queryset)
return super().filter_queryset(queryset)
@extend_schema(
responses={
200: inline_serializer(
"UserPathSerializer", {"paths": ListField(child=CharField(), read_only=True)}
)
},
parameters=[
OpenApiParameter(
name="search",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
)
],
)
@action(detail=False, pagination_class=None)
def paths(self, request: Request) -> Response:
"""Get all user paths"""
return Response(
data={
"paths": list(
self.filter_queryset(self.get_queryset())
.values("path")
.distinct()
.order_by("path")
.values_list("path", flat=True)
)
}
)

View File

@ -2,6 +2,7 @@
from importlib import import_module
from django.apps import AppConfig
from django.conf import settings
class AuthentikCoreConfig(AppConfig):
@ -15,3 +16,7 @@ class AuthentikCoreConfig(AppConfig):
def ready(self):
import_module("authentik.core.signals")
import_module("authentik.core.managed")
if settings.DEBUG:
from authentik.root.celery import worker_ready_hook
worker_ready_hook()

View File

@ -0,0 +1,13 @@
"""Run bootstrap tasks"""
from django.core.management.base import BaseCommand
from authentik.root.celery import _get_startup_tasks
class Command(BaseCommand): # pragma: no cover
"""Run bootstrap tasks to ensure certain objects are created"""
def handle(self, **options):
tasks = _get_startup_tasks()
for task in tasks:
task()

View File

@ -2,6 +2,7 @@
from django.apps import apps
from django.contrib.auth.management import create_permissions
from django.core.management.base import BaseCommand, no_translations
from guardian.management import create_anonymous_user
class Command(BaseCommand): # pragma: no cover
@ -13,3 +14,4 @@ class Command(BaseCommand): # pragma: no cover
for app in apps.get_app_configs():
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
create_permissions(app, verbosity=0)
create_anonymous_user(None, using="default")

View File

@ -56,7 +56,7 @@ class RequestIDMiddleware:
response[RESPONSE_HEADER_ID] = request.request_id
setattr(response, "ak_context", {})
response.ak_context.update(LOCAL.authentik)
response.ak_context[KEY_USER] = request.user.username
response.ak_context.setdefault(KEY_USER, request.user.username)
for key in list(LOCAL.authentik.keys()):
del LOCAL.authentik[key]
return response

View File

@ -12,9 +12,9 @@ import authentik.core.models
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import User
from django.contrib.auth.hashers import make_password
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
akadmin, _ = User.objects.using(db_alias).get_or_create(
@ -28,9 +28,9 @@ def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
if "AUTHENTIK_BOOTSTRAP_PASSWORD" in environ:
password = environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]
if password:
akadmin.set_password(password, signal=False)
akadmin.password = make_password(password)
else:
akadmin.set_unusable_password()
akadmin.password = make_password(None)
akadmin.save()

View File

@ -8,9 +8,9 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import User
from django.contrib.auth.hashers import make_password
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
akadmin, _ = User.objects.using(db_alias).get_or_create(
@ -24,9 +24,9 @@ def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
if "AUTHENTIK_BOOTSTRAP_PASSWORD" in environ:
password = environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]
if password:
akadmin.set_password(password, signal=False)
akadmin.password = make_password(password)
else:
akadmin.set_unusable_password()
akadmin.password = make_password(None)
akadmin.save()

View File

@ -36,8 +36,10 @@ def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import Token, TokenIntents, User
from authentik.core.models import TokenIntents
User = apps.get_model("authentik_core", "User")
Token = apps.get_model("authentik_core", "Token")
db_alias = schema_editor.connection.alias

View File

@ -0,0 +1,23 @@
# Generated by Django 4.0.5 on 2022-06-13 18:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0020_application_open_in_new_tab"),
]
operations = [
migrations.AddField(
model_name="source",
name="user_path_template",
field=models.TextField(default="goauthentik.io/sources/%(slug)s"),
),
migrations.AddField(
model_name="user",
name="path",
field=models.TextField(default="users"),
),
]

View File

@ -7,8 +7,10 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import Token, TokenIntents, User
from authentik.core.models import TokenIntents
User = apps.get_model("authentik_core", "User")
Token = apps.get_model("authentik_core", "Token")
db_alias = schema_editor.connection.alias

View File

@ -46,6 +46,9 @@ USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts"
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("dist/assets/images/user_default.png")
@ -103,7 +106,10 @@ class Group(models.Model):
SELECT authentik_core_group.*, parents.relative_depth - 1
FROM authentik_core_group,parents
WHERE authentik_core_group.parent_id = parents.group_uuid
WHERE (
authentik_core_group.parent_id = parents.group_uuid and
parents.relative_depth > -20
)
)
SELECT group_uuid
FROM parents
@ -138,6 +144,7 @@ class User(GuardianUserMixin, AbstractUser):
uuid = models.UUIDField(default=uuid4, editable=False)
name = models.TextField(help_text=_("User's display name."))
path = models.TextField(default="users")
sources = models.ManyToManyField("Source", through="UserSourceConnection")
ak_groups = models.ManyToManyField("Group", related_name="users")
@ -147,6 +154,11 @@ class User(GuardianUserMixin, AbstractUser):
objects = UserManager()
@staticmethod
def default_path() -> str:
"""Get the default user path"""
return User._meta.get_field("path").default
def group_attributes(self, request: Optional[HttpRequest] = None) -> dict[str, Any]:
"""Get a dictionary containing the attributes from all groups the user belongs to,
including the users attributes"""
@ -373,6 +385,8 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
name = models.TextField(help_text=_("Source's display Name."))
slug = models.SlugField(help_text=_("Internal source name, used in URLs."), unique=True)
user_path_template = models.TextField(default="goauthentik.io/sources/%(slug)s")
enabled = models.BooleanField(default=True)
property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True)
@ -408,6 +422,17 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
objects = InheritanceManager()
def get_user_path(self) -> str:
"""Get user path, fallback to default for formatting errors"""
try:
return self.user_path_template % {
"slug": self.slug,
}
# pylint: disable=broad-except
except Exception as exc:
LOGGER.warning("Failed to template user path", exc=exc, source=self)
return User.default_path()
@property
def component(self) -> str:
"""Return component used to edit this object"""
@ -457,8 +482,9 @@ class ExpiringModel(models.Model):
def filter_not_expired(cls, **kwargs) -> QuerySet:
"""Filer for tokens which are not expired yet or are not expiring,
and match filters in `kwargs`"""
expired = Q(expires__lt=now(), expiring=True)
return cls.objects.exclude(expired).filter(**kwargs)
for obj in cls.objects.filter(**kwargs).filter(Q(expires__lt=now(), expiring=True)):
obj.delete()
return cls.objects.filter(**kwargs)
@property
def is_expired(self) -> bool:

View File

@ -26,11 +26,11 @@ from authentik.flows.planner import (
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.types import PolicyResult
from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH
class Action(Enum):
@ -165,9 +165,9 @@ class SourceFlowManager:
return self.handle_enroll(connection)
except FlowNonApplicableException as exc:
self._logger.warning("Flow non applicable", exc=exc)
return self.error_handler(exc, exc.policy_result)
return self.error_handler(exc)
# Default case, assume deny
error = (
error = Exception(
_(
(
"Request to authenticate with %(source)s has been denied. Please authenticate "
@ -178,14 +178,13 @@ class SourceFlowManager:
)
return self.error_handler(error)
def error_handler(
self, error: Exception, policy_result: Optional[PolicyResult] = None
) -> HttpResponse:
def error_handler(self, error: Exception) -> HttpResponse:
"""Handle any errors by returning an access denied stage"""
response = AccessDeniedResponse(self.request)
response.error_message = str(error)
if policy_result:
response.policy_result = policy_result
if isinstance(error, FlowNonApplicableException):
response.policy_result = error.policy_result
response.error_message = error.messages
return response
# pylint: disable=unused-argument
@ -291,5 +290,6 @@ class SourceFlowManager:
connection,
**{
PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info),
PLAN_CONTEXT_USER_PATH: self.source.get_user_path(),
},
)

View File

@ -10,7 +10,9 @@
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
<script>
window.authentik = {};
window.authentik = {
"locale": "{{ tenant.default_locale }}",
};
window.authentik.flow = {
"layout": "{{ flow.layout }}",
};

View File

@ -2,6 +2,7 @@
from django.test.testcases import TestCase
from authentik.core.models import Group, User
from authentik.lib.generators import generate_id
class TestGroups(TestCase):
@ -9,32 +10,43 @@ class TestGroups(TestCase):
def test_group_membership_simple(self):
"""Test simple membership"""
user = User.objects.create(username="user")
user2 = User.objects.create(username="user2")
group = Group.objects.create(name="group")
user = User.objects.create(username=generate_id())
user2 = User.objects.create(username=generate_id())
group = Group.objects.create(name=generate_id())
group.users.add(user)
self.assertTrue(group.is_member(user))
self.assertFalse(group.is_member(user2))
def test_group_membership_parent(self):
"""Test parent membership"""
user = User.objects.create(username="user")
user2 = User.objects.create(username="user2")
first = Group.objects.create(name="first")
second = Group.objects.create(name="second", parent=first)
user = User.objects.create(username=generate_id())
user2 = User.objects.create(username=generate_id())
first = Group.objects.create(name=generate_id())
second = Group.objects.create(name=generate_id(), parent=first)
second.users.add(user)
self.assertTrue(first.is_member(user))
self.assertFalse(first.is_member(user2))
def test_group_membership_parent_extra(self):
"""Test parent membership"""
user = User.objects.create(username="user")
user2 = User.objects.create(username="user2")
first = Group.objects.create(name="first")
second = Group.objects.create(name="second", parent=first)
third = Group.objects.create(name="third", parent=second)
user = User.objects.create(username=generate_id())
user2 = User.objects.create(username=generate_id())
first = Group.objects.create(name=generate_id())
second = Group.objects.create(name=generate_id(), parent=first)
third = Group.objects.create(name=generate_id(), parent=second)
second.users.add(user)
self.assertTrue(first.is_member(user))
self.assertFalse(first.is_member(user2))
self.assertFalse(third.is_member(user))
self.assertFalse(third.is_member(user2))
def test_group_membership_recursive(self):
"""Test group membership (recursive)"""
user = User.objects.create(username=generate_id())
group = Group.objects.create(name=generate_id())
group2 = Group.objects.create(name=generate_id(), parent=group)
group.users.add(user)
group.parent = group2
group.save()
self.assertTrue(group.is_member(user))
self.assertTrue(group2.is_member(user))

View File

@ -5,7 +5,7 @@ from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
from authentik.flows.models import FlowDesignation
from authentik.lib.generators import generate_key
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.email.models import EmailStage
from authentik.tenants.models import Tenant
@ -149,3 +149,65 @@ class TestUsersAPI(APITestCase):
},
)
self.assertEqual(response.status_code, 400)
def test_paths(self):
"""Test path"""
self.client.force_login(self.admin)
response = self.client.get(
reverse("authentik_api:user-paths"),
)
print(response.content)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(response.content.decode(), {"paths": ["users"]})
def test_path_valid(self):
"""Test path"""
self.client.force_login(self.admin)
response = self.client.post(
reverse("authentik_api:user-list"),
data={"name": generate_id(), "username": generate_id(), "groups": [], "path": "foo"},
)
self.assertEqual(response.status_code, 201)
def test_path_invalid(self):
"""Test path (invalid)"""
self.client.force_login(self.admin)
response = self.client.post(
reverse("authentik_api:user-list"),
data={"name": generate_id(), "username": generate_id(), "groups": [], "path": "/foo"},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(), {"path": ["No leading or trailing slashes allowed."]}
)
self.client.force_login(self.admin)
response = self.client.post(
reverse("authentik_api:user-list"),
data={"name": generate_id(), "username": generate_id(), "groups": [], "path": ""},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(response.content.decode(), {"path": ["This field may not be blank."]})
response = self.client.post(
reverse("authentik_api:user-list"),
data={"name": generate_id(), "username": generate_id(), "groups": [], "path": "foo/"},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(), {"path": ["No leading or trailing slashes allowed."]}
)
response = self.client.post(
reverse("authentik_api:user-list"),
data={
"name": generate_id(),
"username": generate_id(),
"groups": [],
"path": "fos//o",
},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(), {"path": ["No empty segments in user path allowed."]}
)

View File

@ -11,14 +11,13 @@ from authentik.lib.generators import generate_id
from authentik.tenants.models import Tenant
def create_test_flow(designation: FlowDesignation = FlowDesignation.STAGE_CONFIGURATION) -> Flow:
def create_test_flow(
designation: FlowDesignation = FlowDesignation.STAGE_CONFIGURATION, **kwargs
) -> Flow:
"""Generate a flow that can be used for testing"""
uid = generate_id(10)
return Flow.objects.create(
name=uid,
title=uid,
slug=slugify(uid),
designation=designation,
name=uid, title=uid, slug=slugify(uid), designation=designation, **kwargs
)
@ -47,11 +46,11 @@ def create_test_tenant() -> Tenant:
def create_test_cert() -> CertificateKeyPair:
"""Generate a certificate for testing"""
CertificateKeyPair.objects.filter(name="goauthentik.io").delete()
builder = CertificateBuilder()
builder.common_name = "goauthentik.io"
builder.build(
subject_alt_names=["goauthentik.io"],
validity_days=360,
)
builder.name = generate_id()
return builder.save()

View File

@ -14,7 +14,9 @@ from authentik.core.views.session import EndSessionView
urlpatterns = [
path(
"",
login_required(RedirectView.as_view(pattern_name="authentik_core:if-user")),
login_required(
RedirectView.as_view(pattern_name="authentik_core:if-user", query_string=True)
),
name="root-redirect",
),
path(

View File

@ -53,10 +53,7 @@ class CertificateBuilder:
.subject_name(
x509.Name(
[
x509.NameAttribute(
NameOID.COMMON_NAME,
self.common_name,
),
x509.NameAttribute(NameOID.COMMON_NAME, self.common_name),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "authentik"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Self-signed"),
]
@ -65,10 +62,7 @@ class CertificateBuilder:
.issuer_name(
x509.Name(
[
x509.NameAttribute(
NameOID.COMMON_NAME,
f"authentik {__version__}",
),
x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {__version__}"),
]
)
)

View File

@ -2,6 +2,13 @@
from importlib import import_module
from django.apps import AppConfig
from prometheus_client import Gauge
GAUGE_TASKS = Gauge(
"authentik_system_tasks",
"System tasks and their status",
["task_name", "task_uid", "status"],
)
class AuthentikEventsConfig(AppConfig):

View File

@ -16,6 +16,7 @@ from authentik.core.models import AuthenticatedSession, User
from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread
from authentik.events.utils import model_to_dict
from authentik.flows.models import FlowToken
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
@ -26,6 +27,7 @@ IGNORED_MODELS = [
AuthenticatedSession,
StaticToken,
Session,
FlowToken,
]
if settings.DEBUG:
from silk.models import Request, Response, SQLQuery

View File

@ -8,18 +8,12 @@ from typing import Any, Optional
from celery import Task
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
from prometheus_client import Gauge
from structlog.stdlib import get_logger
from authentik.events.apps import GAUGE_TASKS
from authentik.events.models import Event, EventAction
from authentik.lib.utils.errors import exception_to_string
GAUGE_TASKS = Gauge(
"authentik_system_tasks",
"System tasks and their status",
["task_name", "task_uid", "status"],
)
LOGGER = get_logger()

View File

@ -73,6 +73,7 @@ class FlowSerializer(ModelSerializer):
"compatibility_mode",
"export_url",
"layout",
"denied_action",
]
extra_kwargs = {
"background": {"read_only": True},
@ -110,8 +111,8 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
serializer_class = FlowSerializer
lookup_field = "slug"
ordering = ["slug", "name"]
search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"]
search_fields = ["name", "slug", "designation", "title", "denied_action"]
filterset_fields = ["flow_uuid", "name", "slug", "designation", "denied_action"]
@permission_required(None, ["authentik_flows.view_flow_cache"])
@extend_schema(responses={200: CacheSerializer(many=False)})
@ -371,7 +372,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
request,
_(
"Flow not applicable to current user/request: %(messages)s"
% {"messages": str(exc)}
% {"messages": exc.messages}
),
)
return Response(

View File

@ -3,9 +3,20 @@ from importlib import import_module
from django.apps import AppConfig
from django.db.utils import ProgrammingError
from prometheus_client import Gauge, Histogram
from authentik.lib.utils.reflection import all_subclasses
GAUGE_FLOWS_CACHED = Gauge(
"authentik_flows_cached",
"Cached flows",
)
HIST_FLOWS_PLAN_TIME = Histogram(
"authentik_flows_plan_time",
"Duration to build a plan for a flow",
["flow_slug"],
)
class AuthentikFlowsConfig(AppConfig):
"""authentik flows app config"""

View File

@ -1,6 +1,6 @@
"""Challenge helpers"""
from enum import Enum
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, TypedDict
from django.db import models
from django.http import JsonResponse
@ -95,6 +95,13 @@ class AccessDeniedChallenge(WithUserInfoChallenge):
component = CharField(default="ak-stage-access-denied")
class PermissionDict(TypedDict):
"""Consent Permission"""
id: str
name: str
class PermissionSerializer(PassiveSerializer):
"""Permission used for consent"""

View File

@ -1,4 +1,5 @@
"""flow exceptions"""
from django.utils.translation import gettext_lazy as _
from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.types import PolicyResult
@ -9,6 +10,13 @@ class FlowNonApplicableException(SentryIgnoredException):
policy_result: PolicyResult
@property
def messages(self) -> str:
"""Get messages from policy result, fallback to generic reason"""
if len(self.policy_result.messages) < 1:
return _("Flow does not apply to current user (denied by policy).")
return "\n".join(self.policy_result.messages)
class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages."""

View File

@ -47,7 +47,8 @@ class ReevaluateMarker(StageMarker):
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
LOGGER.debug(
"f(plan_inst)[re-eval marker]: running re-evaluation",
"f(plan_inst): running re-evaluation",
marker="ReevaluateMarker",
binding=binding,
policy_binding=self.binding,
)
@ -56,13 +57,15 @@ class ReevaluateMarker(StageMarker):
)
engine.use_cache = False
engine.request.set_http_request(http_request)
engine.request.context = plan.context
engine.request.context["flow_plan"] = plan
engine.request.context.update(plan.context)
engine.build()
result = engine.result
if result.passing:
return binding
LOGGER.warning(
"f(plan_inst)[re-eval marker]: binding failed re-evaluation",
"f(plan_inst): binding failed re-evaluation",
marker="ReevaluateMarker",
binding=binding,
messages=result.messages,
)

View File

@ -14,7 +14,7 @@ return not akadmin.has_usable_password()"""
PREFILL_POLICY_EXPRESSION = """# This policy sets the user for the currently running flow
# by injecting "pending_user"
akadmin = ak_user_by(username="akadmin")
context["pending_user"] = akadmin
context["flow_plan"].context["pending_user"] = akadmin
return True"""

View File

@ -0,0 +1,26 @@
# Generated by Django 4.0.5 on 2022-07-02 12:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0022_flow_layout"),
]
operations = [
migrations.AddField(
model_name="flow",
name="denied_action",
field=models.TextField(
choices=[
("message_continue", "Message Continue"),
("message", "Message"),
("continue", "Continue"),
],
default="message_continue",
help_text="Configure what should happen when a flow denies access to a user.",
),
),
]

View File

@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Optional
from uuid import uuid4
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
@ -40,6 +39,14 @@ class InvalidResponseAction(models.TextChoices):
RESTART_WITH_CONTEXT = "restart_with_context"
class FlowDeniedAction(models.TextChoices):
"""Configure what response is given to denied flow executions"""
MESSAGE_CONTINUE = "message_continue"
MESSAGE = "message"
CONTINUE = "continue"
class FlowDesignation(models.TextChoices):
"""Designation of what a Flow should be used for. At a later point, this
should be replaced by a database entry."""
@ -87,13 +94,15 @@ class Stage(SerializerModel):
return f"Stage {self.name}"
def in_memory_stage(view: type["StageView"]) -> Stage:
def in_memory_stage(view: type["StageView"], **kwargs) -> Stage:
"""Creates an in-memory stage instance, based on a `view` as view."""
stage = Stage()
# Because we can't pickle a locally generated function,
# we set the view as a separate property and reference a generic function
# that returns that member
setattr(stage, "__in_memory_type", view)
for key, value in kwargs.items():
setattr(stage, key, value)
return stage
@ -137,6 +146,12 @@ class Flow(SerializerModel, PolicyBindingModel):
),
)
denied_action = models.TextField(
choices=FlowDeniedAction.choices,
default=FlowDeniedAction.MESSAGE_CONTINUE,
help_text=_("Configure what should happen when a flow denies access to a user."),
)
@property
def background_url(self) -> str:
"""Get the URL to the background image. If the name is /static or starts with http
@ -155,23 +170,6 @@ class Flow(SerializerModel, PolicyBindingModel):
return FlowSerializer
@staticmethod
def with_policy(request: HttpRequest, **flow_filter) -> Optional["Flow"]:
"""Get a Flow by `**flow_filter` and check if the request from `request` can access it."""
from authentik.policies.engine import PolicyEngine
flows = Flow.objects.filter(**flow_filter).order_by("slug")
for flow in flows:
engine = PolicyEngine(flow, request.user, request)
engine.build()
result = engine.result
if result.passing:
LOGGER.debug("with_policy: flow passing", flow=flow)
return flow
LOGGER.warning("with_policy: flow not passing", flow=flow, messages=result.messages)
LOGGER.debug("with_policy: no flow found", filters=flow_filter)
return None
def __str__(self) -> str:
return f"Flow {self.name} ({self.slug})"

View File

@ -4,16 +4,16 @@ from typing import Any, Optional
from django.core.cache import cache
from django.http import HttpRequest
from prometheus_client import Gauge, Histogram
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import User
from authentik.events.models import cleanse_dict
from authentik.flows.apps import HIST_FLOWS_PLAN_TIME
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage, in_memory_stage
from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine
@ -26,15 +26,6 @@ PLAN_CONTEXT_SOURCE = "source"
# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan
# was restored.
PLAN_CONTEXT_IS_RESTORED = "is_restored"
GAUGE_FLOWS_CACHED = Gauge(
"authentik_flows_cached",
"Cached flows",
)
HIST_FLOWS_PLAN_TIME = Histogram(
"authentik_flows_plan_time",
"Duration to build a plan for a flow",
["flow_slug"],
)
CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_flows"))
@ -71,6 +62,12 @@ class FlowPlan:
self.bindings.insert(1, FlowStageBinding(stage=stage, order=0))
self.markers.insert(1, marker or StageMarker())
def redirect(self, destination: str):
"""Insert a redirect stage as next stage"""
from authentik.flows.stage import RedirectStage
self.insert_stage(in_memory_stage(RedirectStage, destination=destination))
def next(self, http_request: Optional[HttpRequest]) -> Optional[FlowStageBinding]:
"""Return next pending stage from the bottom of the list"""
if not self.has_stages:
@ -146,11 +143,11 @@ class FlowPlanner:
engine = PolicyEngine(self.flow, user, request)
if default_context:
span.set_data("default_context", cleanse_dict(default_context))
engine.request.context = default_context
engine.request.context.update(default_context)
engine.build()
result = engine.result
if not result.passing:
exc = FlowNonApplicableException(",".join(result.messages))
exc = FlowNonApplicableException()
exc.policy_result = result
raise exc
# User is passing so far, check if we have a cached plan
@ -207,7 +204,8 @@ class FlowPlanner:
stage=binding.stage,
)
engine = PolicyEngine(binding, user, request)
engine.request.context = plan.context
engine.request.context["flow_plan"] = plan
engine.request.context.update(plan.context)
engine.build()
if engine.passing:
self._logger.debug(

View File

@ -4,7 +4,7 @@ from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.flows.planner import GAUGE_FLOWS_CACHED
from authentik.flows.apps import GAUGE_FLOWS_CACHED
from authentik.root.monitoring import monitoring_set
LOGGER = get_logger()

View File

@ -19,6 +19,7 @@ from authentik.flows.challenge import (
ChallengeTypes,
ContextualFlowInfo,
HttpChallengeResponse,
RedirectChallenge,
WithUserInfoChallenge,
)
from authentik.flows.models import InvalidResponseAction
@ -219,3 +220,21 @@ class AccessDeniedChallengeView(ChallengeStageView):
# .get() method is called
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: # pragma: no cover
return self.executor.cancel()
class RedirectStage(ChallengeStageView):
"""Redirect to any URL"""
def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
destination = getattr(
self.executor.current_stage, "destination", reverse("authentik_core:root-redirect")
)
return RedirectChallenge(
data={
"type": ChallengeTypes.REDIRECT.value,
"to": destination,
}
)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return HttpChallengeResponse(self.get_challenge())

View File

@ -6,14 +6,20 @@ from django.test.client import RequestFactory
from django.urls import reverse
from authentik.core.models import User
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.core.tests.utils import create_test_flow
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.flows.models import (
FlowDeniedAction,
FlowDesignation,
FlowStageBinding,
InvalidResponseAction,
)
from authentik.flows.planner import FlowPlan, FlowPlanner
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding
from authentik.policies.reputation.models import ReputationPolicy
@ -22,7 +28,7 @@ from authentik.stages.deny.models import DenyStage
from authentik.stages.dummy.models import DummyStage
from authentik.stages.identification.models import IdentificationStage, UserFields
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False, "foo"))
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
@ -47,12 +53,10 @@ class TestFlowExecutor(FlowTestCase):
)
def test_existing_plan_diff_flow(self):
"""Check that a plan for a different flow cancels the current plan"""
flow = Flow.objects.create(
name="test-existing-plan-diff",
slug="test-existing-plan-diff",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
stage = DummyStage.objects.create(name="dummy")
stage = DummyStage.objects.create(name=generate_id())
binding = FlowStageBinding(target=flow, stage=stage, order=0)
plan = FlowPlan(flow_pk=flow.pk.hex + "a", bindings=[binding], markers=[StageMarker()])
session = self.client.session
@ -77,10 +81,8 @@ class TestFlowExecutor(FlowTestCase):
)
def test_invalid_non_applicable_flow(self):
"""Tests that a non-applicable flow returns the correct error message"""
flow = Flow.objects.create(
name="test-non-applicable",
slug="test-non-applicable",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
CONFIG.update_from_dict({"domain": "testserver"})
@ -90,7 +92,7 @@ class TestFlowExecutor(FlowTestCase):
self.assertStageResponse(
response,
flow=flow,
error_message=FlowNonApplicableException.__doc__,
error_message="foo",
component="ak-stage-access-denied",
)
@ -98,12 +100,15 @@ class TestFlowExecutor(FlowTestCase):
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_empty_flow(self):
"""Tests that an empty flow returns the correct error message"""
flow = Flow.objects.create(
name="test-empty",
slug="test-empty",
designation=FlowDesignation.AUTHENTICATION,
@patch(
"authentik.policies.engine.PolicyEngine.result",
POLICY_RETURN_FALSE,
)
def test_invalid_non_applicable_flow_continue(self):
"""Tests that a non-applicable flow that should redirect"""
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
denied_action=FlowDeniedAction.CONTINUE,
)
CONFIG.update_from_dict({"domain": "testserver"})
@ -119,10 +124,8 @@ class TestFlowExecutor(FlowTestCase):
)
def test_invalid_flow_redirect(self):
"""Tests that an invalid flow still redirects"""
flow = Flow.objects.create(
name="test-empty",
slug="test-empty",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
CONFIG.update_from_dict({"domain": "testserver"})
@ -132,18 +135,33 @@ class TestFlowExecutor(FlowTestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_empty_flow(self):
"""Tests that an empty flow returns the correct error message"""
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
CONFIG.update_from_dict({"domain": "testserver"})
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
def test_multi_stage_flow(self):
"""Test a full flow with multiple stages"""
flow = Flow.objects.create(
name="test-full",
slug="test-full",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy2"), order=1
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=1
)
exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
@ -170,19 +188,19 @@ class TestFlowExecutor(FlowTestCase):
)
def test_reevaluate_remove_last(self):
"""Test planner with re-evaluate (last stage is removed)"""
flow = Flow.objects.create(
name="test-default-context",
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
false_policy = DummyPolicy.objects.create(
name=generate_id(), result=False, wait_min=1, wait_max=2
)
false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)
binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
binding2 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
stage=DummyStage.objects.create(name=generate_id()),
order=1,
re_evaluate_policies=True,
)
@ -217,24 +235,24 @@ class TestFlowExecutor(FlowTestCase):
def test_reevaluate_remove_middle(self):
"""Test planner with re-evaluate (middle stage is removed)"""
flow = Flow.objects.create(
name="test-default-context",
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
false_policy = DummyPolicy.objects.create(
name=generate_id(), result=False, wait_min=1, wait_max=2
)
false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)
binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
binding2 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
stage=DummyStage.objects.create(name=generate_id()),
order=1,
re_evaluate_policies=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy3"), order=2
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
)
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
@ -277,24 +295,24 @@ class TestFlowExecutor(FlowTestCase):
def test_reevaluate_keep(self):
"""Test planner with re-evaluate (everything is kept)"""
flow = Flow.objects.create(
name="test-default-context",
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
true_policy = DummyPolicy.objects.create(
name=generate_id(), result=True, wait_min=1, wait_max=2
)
true_policy = DummyPolicy.objects.create(result=True, wait_min=1, wait_max=2)
binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
binding2 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
stage=DummyStage.objects.create(name=generate_id()),
order=1,
re_evaluate_policies=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy3"), order=2
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
)
PolicyBinding.objects.create(policy=true_policy, target=binding2, order=0)
@ -347,30 +365,30 @@ class TestFlowExecutor(FlowTestCase):
def test_reevaluate_remove_consecutive(self):
"""Test planner with re-evaluate (consecutive stages are removed)"""
flow = Flow.objects.create(
name="test-default-context",
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
false_policy = DummyPolicy.objects.create(
name=generate_id(), result=False, wait_min=1, wait_max=2
)
false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)
binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
binding2 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
stage=DummyStage.objects.create(name=generate_id()),
order=1,
re_evaluate_policies=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy3"),
stage=DummyStage.objects.create(name=generate_id()),
order=2,
re_evaluate_policies=True,
)
binding4 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy4"), order=2
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
)
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
@ -415,13 +433,11 @@ class TestFlowExecutor(FlowTestCase):
def test_stageview_user_identifier(self):
"""Test PLAN_CONTEXT_PENDING_USER_IDENTIFIER"""
flow = Flow.objects.create(
name="test-default-context",
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
ident = "test-identifier"
@ -443,10 +459,8 @@ class TestFlowExecutor(FlowTestCase):
def test_invalid_restart(self):
"""Test flow that restarts on invalid entry"""
flow = Flow.objects.create(
name="restart-on-invalid",
slug="restart-on-invalid",
designation=FlowDesignation.AUTHENTICATION,
flow = create_test_flow(
FlowDesignation.AUTHENTICATION,
)
# Stage 0 is a deny stage that is added dynamically
# when the reputation policy says so

View File

@ -27,6 +27,7 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"promptstage_set",
"policybindingmodel_ptr_id",
"export_url",
"meta_model_name",
)
for to_remove_name in to_remove:
if to_remove_name in data:

View File

@ -10,6 +10,7 @@ from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect
from django.http.request import QueryDict
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
@ -37,6 +38,7 @@ from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableExce
from authentik.flows.models import (
ConfigurableStage,
Flow,
FlowDeniedAction,
FlowDesignation,
FlowStageBinding,
FlowToken,
@ -54,6 +56,7 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.policies.engine import PolicyEngine
from authentik.tenants.models import Tenant
LOGGER = get_logger()
@ -129,21 +132,27 @@ class FlowExecutorView(APIView):
self._logger = get_logger().bind(flow_slug=flow_slug)
set_tag("authentik.flow", self.flow.slug)
def handle_invalid_flow(self, exc: BaseException) -> HttpResponse:
def handle_invalid_flow(self, exc: FlowNonApplicableException) -> HttpResponse:
"""When a flow is non-applicable check if user is on the correct domain"""
if NEXT_ARG_NAME in self.request.GET:
if not is_url_absolute(self.request.GET.get(NEXT_ARG_NAME)):
if self.flow.denied_action in [
FlowDeniedAction.CONTINUE,
FlowDeniedAction.MESSAGE_CONTINUE,
]:
next_url = self.request.GET.get(NEXT_ARG_NAME)
if next_url and not is_url_absolute(next_url):
self._logger.debug("f(exec): Redirecting to next on fail")
return redirect(self.request.GET.get(NEXT_ARG_NAME))
message = exc.__doc__ if exc.__doc__ else str(exc)
return self.stage_invalid(error_message=message)
return to_stage_response(self.request, redirect(next_url))
if self.flow.denied_action == FlowDeniedAction.CONTINUE:
return to_stage_response(
self.request, redirect(reverse("authentik_core:root-redirect"))
)
return to_stage_response(self.request, self.stage_invalid(error_message=exc.messages))
def _check_flow_token(self, get_params: QueryDict):
def _check_flow_token(self, key: str) -> Optional[FlowPlan]:
"""Check if the user is using a flow token to restore a plan"""
tokens = FlowToken.filter_not_expired(key=get_params[QS_KEY_TOKEN])
if not tokens.exists():
return False
token: FlowToken = tokens.first()
token: Optional[FlowToken] = FlowToken.filter_not_expired(key=key).first()
if not token:
return None
try:
plan = token.plan
except (AttributeError, EOFError, ImportError, IndexError) as exc:
@ -164,7 +173,7 @@ class FlowExecutorView(APIView):
span.set_data("authentik Flow", self.flow.slug)
get_params = QueryDict(request.GET.get("query", ""))
if QS_KEY_TOKEN in get_params:
plan = self._check_flow_token(get_params)
plan = self._check_flow_token(get_params[QS_KEY_TOKEN])
if plan:
self.request.session[SESSION_KEY_PLAN] = plan
# Early check if there's an active Plan for the current session
@ -188,7 +197,7 @@ class FlowExecutorView(APIView):
self.plan = self._initiate_plan()
except FlowNonApplicableException as exc:
self._logger.warning("f(exec): Flow not applicable to current user", exc=exc)
return to_stage_response(self.request, self.handle_invalid_flow(exc))
return self.handle_invalid_flow(exc)
except EmptyFlowException as exc:
self._logger.warning("f(exec): Flow is empty", exc=exc)
# To match behaviour with loading an empty flow plan from cache,
@ -471,6 +480,20 @@ class ToDefaultFlow(View):
designation: Optional[FlowDesignation] = None
def flow_by_policy(self, request: HttpRequest, **flow_filter) -> Optional[Flow]:
"""Get a Flow by `**flow_filter` and check if the request from `request` can access it."""
flows = Flow.objects.filter(**flow_filter).order_by("slug")
for flow in flows:
engine = PolicyEngine(flow, request.user, request)
engine.build()
result = engine.result
if result.passing:
LOGGER.debug("flow_by_policy: flow passing", flow=flow)
return flow
LOGGER.warning("flow_by_policy: flow not passing", flow=flow, messages=result.messages)
LOGGER.debug("flow_by_policy: no flow found", filters=flow_filter)
return None
def dispatch(self, request: HttpRequest) -> HttpResponse:
tenant: Tenant = request.tenant
flow = None
@ -481,7 +504,7 @@ class ToDefaultFlow(View):
flow = tenant.flow_invalidation
# If no flow was set, get the first based on slug and policy
if not flow:
flow = Flow.with_policy(request, designation=self.designation)
flow = self.flow_by_policy(request, designation=self.designation)
# If we still don't have a flow, 404
if not flow:
raise Http404

View File

@ -1,3 +1,4 @@
# update website/docs/installation/configuration.md
# This is the default configuration file
postgresql:
host: localhost
@ -57,6 +58,10 @@ outposts:
container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
discover: true
ldap:
tls:
ciphers: null
cookie_domain: null
disable_update_check: false
disable_startup_analytics: false

View File

@ -1,5 +1,5 @@
"""authentik sentry integration"""
from typing import Optional
from typing import Any, Optional
from aioredis.errors import ConnectionClosedError, ReplyError
from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError
@ -17,7 +17,7 @@ from ldap3.core.exceptions import LDAPException
from redis.exceptions import ConnectionError as RedisConnectionError
from redis.exceptions import RedisError, ResponseError
from rest_framework.exceptions import APIException
from sentry_sdk import Hub
from sentry_sdk import HttpTransport, Hub
from sentry_sdk import init as sentry_sdk_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.celery import CeleryIntegration
@ -30,6 +30,7 @@ from websockets.exceptions import WebSocketException
from authentik import __version__, get_build_hash
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import class_to_path, get_env
LOGGER = get_logger()
@ -52,11 +53,18 @@ class SentryIgnoredException(Exception):
"""Base Class for all errors that are suppressed, and not sent to sentry."""
class SentryTransport(HttpTransport):
"""Custom sentry transport with custom user-agent"""
def __init__(self, options: dict[str, Any]) -> None:
super().__init__(options)
self._auth = self.parsed_dsn.to_auth(authentik_user_agent())
def sentry_init(**sentry_init_kwargs):
"""Configure sentry SDK"""
sentry_env = CONFIG.y("error_reporting.environment", "customer")
kwargs = {
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.5)),
"environment": sentry_env,
"send_default_pii": CONFIG.y_bool("error_reporting.send_pii", False),
}
@ -71,7 +79,9 @@ def sentry_init(**sentry_init_kwargs):
ThreadingIntegration(propagate_hub=True),
],
before_send=before_send,
traces_sampler=traces_sampler,
release=f"authentik@{__version__}",
transport=SentryTransport,
**kwargs,
)
set_tag("authentik.build_hash", get_build_hash("tagged"))
@ -83,6 +93,15 @@ def sentry_init(**sentry_init_kwargs):
)
def traces_sampler(sampling_context: dict) -> float:
"""Custom sampler to ignore certain routes"""
path = sampling_context.get("asgi_scope", {}).get("path", "")
# Ignore all healthcheck routes
if path.startswith("/-/health") or path.startswith("/-/metrics"):
return 0
return float(CONFIG.y("error_reporting.sample_rate", 0.5))
def before_send(event: dict, hint: dict) -> Optional[dict]:
"""Check if error is database error, and ignore if so"""
# pylint: disable=no-name-in-module

View File

@ -1,10 +1,18 @@
"""error utils"""
from traceback import format_tb
from traceback import extract_tb
TRACEBACK_HEADER = "Traceback (most recent call last):\n"
from authentik.lib.utils.reflection import class_to_path
TRACEBACK_HEADER = "Traceback (most recent call last):"
def exception_to_string(exc: Exception) -> str:
"""Convert exception to string stackrace"""
# Either use passed original exception or whatever we have
return TRACEBACK_HEADER + "".join(format_tb(exc.__traceback__)) + str(exc)
return "\n".join(
[
TRACEBACK_HEADER,
*[x.rstrip() for x in extract_tb(exc.__traceback__).format()],
f"{class_to_path(exc.__class__)}: {str(exc)}",
]
)

View File

@ -8,3 +8,8 @@ class AuthentikManagedConfig(AppConfig):
name = "authentik.managed"
label = "authentik_managed"
verbose_name = "authentik Managed"
def ready(self) -> None:
from authentik.managed.tasks import managed_reconcile
managed_reconcile.delay()

View File

@ -11,7 +11,11 @@ from authentik.events.monitored_tasks import (
from authentik.managed.manager import ObjectManager
@CELERY_APP.task(bind=True, base=MonitoredTask)
@CELERY_APP.task(
bind=True,
base=MonitoredTask,
retry_backoff=True,
)
@prefill_task
def managed_reconcile(self: MonitoredTask):
"""Run ObjectManager to ensure objects are up-to-date"""
@ -22,3 +26,4 @@ def managed_reconcile(self: MonitoredTask):
)
except DatabaseError as exc: # pragma: no cover
self.set_status(TaskResult(TaskResultStatus.WARNING, [str(exc)]))
self.retry()

View File

@ -2,10 +2,20 @@
from importlib import import_module
from django.apps import AppConfig
from prometheus_client import Gauge
from structlog.stdlib import get_logger
LOGGER = get_logger()
GAUGE_OUTPOSTS_CONNECTED = Gauge(
"authentik_outposts_connected", "Currently connected outposts", ["outpost", "uid", "expected"]
)
GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
"authentik_outposts_last_update",
"Last update from any outpost",
["outpost", "uid", "version"],
)
class AuthentikOutpostConfig(AppConfig):
"""authentik outposts app config"""

View File

@ -8,21 +8,12 @@ from channels.exceptions import DenyConnection
from dacite import from_dict
from dacite.data import Data
from guardian.shortcuts import get_objects_for_user
from prometheus_client import Gauge
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.channels import AuthJsonConsumer
from authentik.outposts.apps import GAUGE_OUTPOSTS_CONNECTED, GAUGE_OUTPOSTS_LAST_UPDATE
from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState
GAUGE_OUTPOSTS_CONNECTED = Gauge(
"authentik_outposts_connected", "Currently connected outposts", ["outpost", "uid", "expected"]
)
GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
"authentik_outposts_last_update",
"Last update from any outpost",
["outpost", "uid", "version"],
)
class WebsocketMessageInstruction(IntEnum):
"""Commands which can be triggered over Websocket"""

View File

@ -20,6 +20,7 @@ from authentik import __version__, get_build_hash
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
USER_ATTRIBUTE_SA,
USER_PATH_SYSTEM_PREFIX,
Provider,
Token,
TokenIntents,
@ -39,6 +40,8 @@ OUR_VERSION = parse(__version__)
OUTPOST_HELLO_INTERVAL = 10
LOGGER = get_logger()
USER_PATH_OUTPOSTS = USER_PATH_SYSTEM_PREFIX + "/outposts"
class ServiceConnectionInvalid(SentryIgnoredException):
"""Exception raised when a Service Connection has invalid parameters"""
@ -328,19 +331,18 @@ class Outpost(ManagedModel):
@property
def user(self) -> User:
"""Get/create user with access to all required objects"""
users = User.objects.filter(username=self.user_identifier)
should_create_user = not users.exists()
if should_create_user:
user = User.objects.filter(username=self.user_identifier).first()
user_created = False
if not user:
user: User = User.objects.create(username=self.user_identifier)
user.set_unusable_password()
user.save()
else:
user = users.first()
user_created = True
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.name = f"Outpost {self.name} Service-Account"
user.path = USER_PATH_OUTPOSTS
user.save()
if should_create_user:
if user_created:
self.build_user_permissions(user)
return user

View File

@ -5,7 +5,6 @@ from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
@ -158,7 +157,7 @@ class PolicyViewSet(
pk=test_params.validated_data["user"].pk
)
if not users.exists():
raise PermissionDenied()
return Response(status=400)
p_request = PolicyRequest(users.first())
p_request.debug = True

View File

@ -2,6 +2,29 @@
from importlib import import_module
from django.apps import AppConfig
from prometheus_client import Gauge, Histogram
GAUGE_POLICIES_CACHED = Gauge(
"authentik_policies_cached",
"Cached Policies",
)
HIST_POLICIES_BUILD_TIME = Histogram(
"authentik_policies_build_time",
"Execution times complete policy result to an object",
["object_pk", "object_type"],
)
HIST_POLICIES_EXECUTION_TIME = Histogram(
"authentik_policies_execution_time",
"Execution times for single policies",
[
"binding_order",
"binding_target_type",
"binding_target_name",
"object_pk",
"object_type",
],
)
class AuthentikPoliciesConfig(AppConfig):

View File

@ -5,26 +5,17 @@ from typing import Iterator, Optional
from django.core.cache import cache
from django.http import HttpRequest
from prometheus_client import Gauge, Histogram
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import User
from authentik.policies.apps import HIST_POLICIES_BUILD_TIME
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel, PolicyEngineMode
from authentik.policies.process import PolicyProcess, cache_key
from authentik.policies.types import PolicyRequest, PolicyResult
CURRENT_PROCESS = current_process()
GAUGE_POLICIES_CACHED = Gauge(
"authentik_policies_cached",
"Cached Policies",
)
HIST_POLICIES_BUILD_TIME = Histogram(
"authentik_policies_build_time",
"Execution times complete policy result to an object",
["object_pk", "object_type"],
)
class PolicyProcessInfo:

View File

@ -4,7 +4,6 @@ from multiprocessing.connection import Connection
from typing import Optional
from django.core.cache import cache
from prometheus_client import Histogram
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog.stdlib import get_logger
@ -12,6 +11,7 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
from authentik.policies.types import PolicyRequest, PolicyResult
@ -21,17 +21,6 @@ LOGGER = get_logger()
FORK_CTX = get_context("fork")
CACHE_TIMEOUT = int(CONFIG.y("redis.cache_timeout_policies"))
PROCESS_CLASS = FORK_CTX.Process
HIST_POLICIES_EXECUTION_TIME = Histogram(
"authentik_policies_execution_time",
"Execution times for single policies",
[
"binding_order",
"binding_target_type",
"binding_target_name",
"object_pk",
"object_type",
],
)
def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:

View File

@ -5,7 +5,7 @@ from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.core.api.applications import user_app_cache_key
from authentik.policies.engine import GAUGE_POLICIES_CACHED
from authentik.policies.apps import GAUGE_POLICIES_CACHED
from authentik.root.monitoring import monitoring_set
LOGGER = get_logger()

View File

@ -1,11 +1,13 @@
"""Test policies API"""
from json import loads
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_admin_user
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.types import PolicyResult
class TestPoliciesAPI(APITestCase):
@ -17,8 +19,10 @@ class TestPoliciesAPI(APITestCase):
self.user = create_test_admin_user()
self.client.force_login(self.user)
def test_test_call(self):
@patch("authentik.policies.dummy.models.DummyPolicy.passes")
def test_test_call(self, passes_mock: MagicMock):
"""Test Policy's test endpoint"""
passes_mock.return_value = PolicyResult(True, "dummy")
response = self.client.post(
reverse("authentik_api:policy-test", kwargs={"pk": self.policy.pk}),
data={
@ -28,6 +32,22 @@ class TestPoliciesAPI(APITestCase):
body = loads(response.content.decode())
self.assertEqual(body["passing"], True)
self.assertEqual(body["messages"], ["dummy"])
self.assertEqual(body["log_messages"], [])
def test_test_call_invalid(self):
"""Test invalid policy test"""
response = self.client.post(
reverse("authentik_api:policy-test", kwargs={"pk": self.policy.pk}),
data={},
)
self.assertEqual(response.status_code, 400)
response = self.client.post(
reverse("authentik_api:policy-test", kwargs={"pk": self.policy.pk}),
data={
"user": self.user.pk + 1,
},
)
self.assertEqual(response.status_code, 400)
def test_types(self):
"""Test Policy's types endpoint"""
@ -35,3 +55,17 @@ class TestPoliciesAPI(APITestCase):
reverse("authentik_api:policy-types"),
)
self.assertEqual(response.status_code, 200)
def test_cache_info(self):
"""Test Policy's cache_info endpoint"""
response = self.client.get(
reverse("authentik_api:policy-cache-info"),
)
self.assertEqual(response.status_code, 200)
def test_cache_clear(self):
"""Test Policy's cache_clear endpoint"""
response = self.client.post(
reverse("authentik_api:policy-cache-clear"),
)
self.assertEqual(response.status_code, 204)

View File

@ -117,8 +117,8 @@ class PolicyAccessView(AccessMixin, View):
result = policy_engine.result
LOGGER.debug(
"PolicyAccessView user_has_access",
user=user,
app=self.application,
user=user.username,
app=self.application.slug,
result=result,
)
if not result.passing:

View File

@ -34,7 +34,6 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"sub_mode",
"property_mappings",
"issuer_mode",
"verification_keys",
"jwks_sources",
]

View File

@ -18,6 +18,8 @@ SCOPE_OPENID = "openid"
SCOPE_OPENID_PROFILE = "profile"
SCOPE_OPENID_EMAIL = "email"
SCOPE_AUTHENTIK_API = "goauthentik.io/api"
# Read/write full user (including email)
SCOPE_GITHUB_USER = "user"
# Read user (without email)

View File

@ -95,38 +95,45 @@ class TokenIntrospectionError(OAuth2Error):
class AuthorizeError(OAuth2Error):
"""General Authorization Errors"""
_errors = {
errors = {
# OAuth2 errors.
# https://tools.ietf.org/html/rfc6749#section-4.1.2.1
"invalid_request": "The request is otherwise malformed",
"unauthorized_client": "The client is not authorized to request an "
"authorization code using this method",
"access_denied": "The resource owner or authorization server denied " "the request",
"unsupported_response_type": "The authorization server does not "
"support obtaining an authorization code "
"using this method",
"invalid_scope": "The requested scope is invalid, unknown, or " "malformed",
"unauthorized_client": (
"The client is not authorized to request an authorization code using this method"
),
"access_denied": "The resource owner or authorization server denied the request",
"unsupported_response_type": (
"The authorization server does not support obtaining an authorization code "
"using this method"
),
"invalid_scope": "The requested scope is invalid, unknown, or malformed",
"server_error": "The authorization server encountered an error",
"temporarily_unavailable": "The authorization server is currently "
"unable to handle the request due to a "
"temporary overloading or maintenance of "
"the server",
"temporarily_unavailable": (
"The authorization server is currently unable to handle the request due to a "
"temporary overloading or maintenance of the server"
),
# OpenID errors.
# http://openid.net/specs/openid-connect-core-1_0.html#AuthError
"interaction_required": "The Authorization Server requires End-User "
"interaction of some form to proceed",
"login_required": "The Authorization Server requires End-User " "authentication",
"account_selection_required": "The End-User is required to select a "
"session at the Authorization Server",
"consent_required": "The Authorization Server requires End-User" "consent",
"invalid_request_uri": "The request_uri in the Authorization Request "
"returns an error or contains invalid data",
"invalid_request_object": "The request parameter contains an invalid " "Request Object",
"request_not_supported": "The provider does not support use of the " "request parameter",
"request_uri_not_supported": "The provider does not support use of the "
"request_uri parameter",
"registration_not_supported": "The provider does not support use of "
"the registration parameter",
"interaction_required": (
"The Authorization Server requires End-User interaction of some form to proceed"
),
"login_required": "The Authorization Server requires End-User authentication",
"account_selection_required": (
"The End-User is required to select a session at the Authorization Server"
),
"consent_required": "The Authorization Server requires End-Userconsent",
"invalid_request_uri": (
"The request_uri in the Authorization Request returns an error or contains invalid data"
),
"invalid_request_object": "The request parameter contains an invalid Request Object",
"request_not_supported": "The provider does not support use of the request parameter",
"request_uri_not_supported": (
"The provider does not support use of the request_uri parameter"
),
"registration_not_supported": (
"The provider does not support use of the registration parameter"
),
}
def __init__(
@ -138,7 +145,7 @@ class AuthorizeError(OAuth2Error):
):
super().__init__()
self.error = error
self.description = self._errors[error]
self.description = self.errors[error]
self.redirect_uri = redirect_uri
self.grant_type = grant_type
self.state = state
@ -170,19 +177,25 @@ class TokenError(OAuth2Error):
errors = {
"invalid_request": "The request is otherwise malformed",
"invalid_client": "Client authentication failed (e.g., unknown client, "
"no client authentication included, or unsupported "
"authentication method)",
"invalid_grant": "The provided authorization grant or refresh token is "
"invalid, expired, revoked, does not match the "
"redirection URI used in the authorization request, "
"or was issued to another client",
"unauthorized_client": "The authenticated client is not authorized to "
"use this authorization grant type",
"unsupported_grant_type": "The authorization grant type is not "
"supported by the authorization server",
"invalid_scope": "The requested scope is invalid, unknown, malformed, "
"or exceeds the scope granted by the resource owner",
"invalid_client": (
"Client authentication failed (e.g., unknown client, no client authentication "
"included, or unsupported authentication method)"
),
"invalid_grant": (
"The provided authorization grant or refresh token is invalid, expired, revoked, "
"does not match the redirection URI used in the authorization request, "
"or was issued to another client"
),
"unauthorized_client": (
"The authenticated client is not authorized to use this authorization grant type"
),
"unsupported_grant_type": (
"The authorization grant type is not supported by the authorization server"
),
"invalid_scope": (
"The requested scope is invalid, unknown, malformed, or exceeds the scope "
"granted by the resource owner"
),
}
def __init__(self, error):
@ -191,17 +204,39 @@ class TokenError(OAuth2Error):
self.description = self.errors[error]
class TokenRevocationError(OAuth2Error):
"""
Specific to the revocation endpoint.
See https://tools.ietf.org/html/rfc7662
"""
errors = TokenError.errors | {
"unsupported_token_type": (
"The authorization server does not support the revocation of the presented "
"token type. That is, the client tried to revoke an access token on a server not"
"supporting this feature."
)
}
def __init__(self, error: str):
super().__init__()
self.error = error
self.description = self.errors[error]
class BearerTokenError(OAuth2Error):
"""
OAuth2 errors.
https://tools.ietf.org/html/rfc6750#section-3.1
"""
_errors = {
errors = {
"invalid_request": ("The request is otherwise malformed", 400),
"invalid_token": (
"The access token provided is expired, revoked, malformed, "
"or invalid for other reasons",
(
"The access token provided is expired, revoked, malformed, "
"or invalid for other reasons"
),
401,
),
"insufficient_scope": (
@ -213,6 +248,6 @@ class BearerTokenError(OAuth2Error):
def __init__(self, code):
super().__init__()
self.code = code
error_tuple = self._errors.get(code, ("", ""))
error_tuple = self.errors.get(code, ("", ""))
self.description = error_tuple[0]
self.status = error_tuple[1]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.0.5 on 2022-06-04 21:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0011_oauth2provider_jwks_sources_and_more"),
]
operations = [
migrations.RemoveField(
model_name="oauth2provider",
name="verification_keys",
),
migrations.AlterField(
model_name="oauth2provider",
name="client_type",
field=models.CharField(
choices=[("confidential", "Confidential"), ("public", "Public")],
default="confidential",
help_text="Confidential clients are capable of maintaining the confidentiality of their credentials. Public clients are incapable",
max_length=30,
verbose_name="Client Type",
),
),
]

View File

@ -143,7 +143,10 @@ class OAuth2Provider(Provider):
choices=ClientTypes.choices,
default=ClientTypes.CONFIDENTIAL,
verbose_name=_("Client Type"),
help_text=_(ClientTypes.__doc__),
help_text=_(
"Confidential clients are capable of maintaining the confidentiality "
"of their credentials. Public clients are incapable"
),
)
client_id = models.CharField(
max_length=255,
@ -222,19 +225,6 @@ class OAuth2Provider(Provider):
),
)
verification_keys = models.ManyToManyField(
CertificateKeyPair,
verbose_name=_("Allowed certificates for JWT-based client_credentials"),
help_text=_(
(
"DEPRECATED. JWTs created with the configured "
"certificates can authenticate with this provider."
)
),
related_name="oauth2_providers",
default=None,
blank=True,
)
jwks_sources = models.ManyToManyField(
OAuthSource,
verbose_name=_(
@ -252,7 +242,7 @@ class OAuth2Provider(Provider):
token = RefreshToken(
user=user,
provider=self,
refresh_token=generate_key(),
refresh_token=base64.urlsafe_b64encode(generate_key().encode()).decode(),
expires=timezone.now() + timedelta_from_string(self.token_validity),
scope=scope,
)

View File

@ -3,7 +3,7 @@ from django.test import RequestFactory
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.challenge import ChallengeTypes
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError
@ -39,7 +39,7 @@ class TestAuthorize(OAuthTestCase):
def test_request(self):
"""test request param"""
OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid/Foo",
@ -59,7 +59,7 @@ class TestAuthorize(OAuthTestCase):
def test_invalid_redirect_uri(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
@ -78,10 +78,55 @@ class TestAuthorize(OAuthTestCase):
)
OAuthAuthorizationParams.from_request(request)
def test_invalid_redirect_uri_empty(self):
"""test missing/invalid redirect URI"""
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="",
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "+",
},
)
OAuthAuthorizationParams.from_request(request)
provider.refresh_from_db()
self.assertEqual(provider.redirect_uris, "+")
def test_invalid_redirect_uri_regex(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid?",
)
with self.assertRaises(RedirectUriError):
request = self.factory.get("/", data={"response_type": "code", "client_id": "test"})
OAuthAuthorizationParams.from_request(request)
with self.assertRaises(RedirectUriError):
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://localhost",
},
)
OAuthAuthorizationParams.from_request(request)
def test_redirect_uri_invalid_regex(self):
"""test missing/invalid redirect URI (invalid regex)"""
OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="+",
@ -103,7 +148,7 @@ class TestAuthorize(OAuthTestCase):
def test_empty_redirect_uri(self):
"""test empty redirect URI (configure in provider)"""
OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
)
@ -123,7 +168,7 @@ class TestAuthorize(OAuthTestCase):
def test_response_type(self):
"""test response_type"""
OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid/Foo",
@ -201,7 +246,7 @@ class TestAuthorize(OAuthTestCase):
"""Test full authorization"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="foo://localhost",
@ -237,12 +282,12 @@ class TestAuthorize(OAuthTestCase):
"""Test full authorization"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id="test",
client_secret=generate_key(),
authorization_flow=flow,
redirect_uris="http://localhost",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
@ -277,18 +322,18 @@ class TestAuthorize(OAuthTestCase):
)
self.validate_jwt(token, provider)
def test_full_form_post(self):
def test_full_form_post_id_token(self):
"""Test full authorization (form_post response)"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=flow,
redirect_uris="http://localhost",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
Application.objects.create(name="app", slug="app", provider=provider)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
@ -298,7 +343,7 @@ class TestAuthorize(OAuthTestCase):
data={
"response_type": "id_token",
"response_mode": "form_post",
"client_id": "test",
"client_id": provider.client_id,
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
@ -314,7 +359,7 @@ class TestAuthorize(OAuthTestCase):
"component": "ak-stage-autosubmit",
"type": ChallengeTypes.NATIVE.value,
"url": "http://localhost",
"title": "Redirecting to app...",
"title": f"Redirecting to {app.name}...",
"attrs": {
"access_token": token.access_token,
"id_token": provider.encode(token.id_token.to_dict()),
@ -325,3 +370,48 @@ class TestAuthorize(OAuthTestCase):
},
)
self.validate_jwt(token, provider)
def test_full_form_post_code(self):
"""Test full authorization (form_post response, code type)"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=flow,
redirect_uris="http://localhost",
signing_key=self.keypair,
)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"response_mode": "form_post",
"client_id": provider.client_id,
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "ak-stage-autosubmit",
"type": ChallengeTypes.NATIVE.value,
"url": "http://localhost",
"title": f"Redirecting to {app.name}...",
"attrs": {
"code": code.code,
"state": state,
},
},
)

View File

@ -0,0 +1,98 @@
"""Test introspect view"""
import json
from base64 import b64encode
from dataclasses import asdict
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TesOAuth2Introspection(OAuthTestCase):
"""Test introspect view"""
def setUp(self) -> None:
super().setUp()
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="",
signing_key=create_test_cert(),
)
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.provider
)
self.app.save()
self.user = create_test_admin_user()
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_id(),
refresh_token=generate_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
self.auth = b64encode(
f"{self.provider.client_id}:{self.provider.client_secret}".encode()
).decode()
def test_introspect(self):
"""Test introspect"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-introspection"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={"token": self.token.refresh_token, "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 200)
self.assertJSONEqual(
res.content.decode(),
{
"aud": None,
"sub": "bar",
"exp": None,
"iat": None,
"iss": "foo",
"active": True,
"client_id": self.provider.client_id,
},
)
def test_introspect_invalid_token(self):
"""Test introspect (invalid token)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-introspection"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={"token": generate_id(), "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 200)
self.assertJSONEqual(
res.content.decode(),
{
"active": False,
},
)
def test_introspect_invalid_auth(self):
"""Test introspect (invalid auth)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-introspection"),
HTTP_AUTHORIZATION="Basic qwerqrwe",
data={"token": generate_id(), "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 200)
self.assertJSONEqual(
res.content.decode(),
{
"active": False,
},
)

View File

@ -0,0 +1,74 @@
"""Test revoke view"""
import json
from base64 import b64encode
from dataclasses import asdict
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TesOAuth2Revoke(OAuthTestCase):
"""Test revoke view"""
def setUp(self) -> None:
super().setUp()
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="",
signing_key=create_test_cert(),
)
self.app = Application.objects.create(
name=generate_id(), slug=generate_id(), provider=self.provider
)
self.app.save()
self.user = create_test_admin_user()
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_id(),
refresh_token=generate_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
self.auth = b64encode(
f"{self.provider.client_id}:{self.provider.client_secret}".encode()
).decode()
def test_revoke(self):
"""Test revoke"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={"token": self.token.refresh_token, "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 200)
def test_revoke_invalid(self):
"""Test revoke (invalid token)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION=f"Basic {self.auth}",
data={"token": self.token.refresh_token + "foo", "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 200)
def test_revoke_invalid_auth(self):
"""Test revoke (invalid auth)"""
res = self.client.post(
reverse("authentik_providers_oauth2:token-revoke"),
HTTP_AUTHORIZATION="Basic fqewr",
data={"token": self.token.refresh_token, "token_type_hint": "refresh_token"},
)
self.assertEqual(res.status_code, 401)

View File

@ -5,7 +5,7 @@ from django.test import RequestFactory
from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.constants import (
@ -24,17 +24,17 @@ class TestToken(OAuthTestCase):
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
self.app = Application.objects.create(name="test", slug="test")
self.app = Application.objects.create(name=generate_id(), slug="test")
def test_request_auth_code(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://testserver",
signing_key=create_test_cert(),
redirect_uris="http://TestServer",
signing_key=self.keypair,
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
@ -44,7 +44,7 @@ class TestToken(OAuthTestCase):
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"redirect_uri": "http://testserver",
"redirect_uri": "http://TestServer",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
@ -56,12 +56,12 @@ class TestToken(OAuthTestCase):
def test_request_auth_code_invalid(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://testserver",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
request = self.factory.post(
@ -79,12 +79,12 @@ class TestToken(OAuthTestCase):
def test_request_refresh_token(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
@ -108,12 +108,12 @@ class TestToken(OAuthTestCase):
def test_auth_code_view(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
@ -150,12 +150,12 @@ class TestToken(OAuthTestCase):
def test_refresh_token_view(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider
@ -199,12 +199,12 @@ class TestToken(OAuthTestCase):
def test_refresh_token_view_invalid_origin(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
@ -244,12 +244,12 @@ class TestToken(OAuthTestCase):
def test_refresh_token_revoke(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://testserver",
signing_key=create_test_cert(),
signing_key=self.keypair,
)
# Needs to be assigned to an application for iss to be set
self.app.provider = provider

View File

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

View File

@ -19,9 +19,9 @@ class TestUserinfo(OAuthTestCase):
def setUp(self) -> None:
super().setUp()
ObjectManager().run()
self.app = Application.objects.create(name="test", slug="test")
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
name=generate_id(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=create_test_flow(),

View File

@ -2,12 +2,15 @@
from django.test import TestCase
from jwt import decode
from authentik.core.tests.utils import create_test_cert
from authentik.crypto.models import CertificateKeyPair
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider, RefreshToken
class OAuthTestCase(TestCase):
"""OAuth test helpers"""
keypair: CertificateKeyPair
required_jwt_keys = [
"exp",
"iat",
@ -17,6 +20,11 @@ class OAuthTestCase(TestCase):
"iss",
]
@classmethod
def setUpClass(cls) -> None:
cls.keypair = create_test_cert()
super().setUpClass()
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider):
"""Validate that all required fields are set"""
key, alg = provider.get_jwt_key()

View File

@ -10,6 +10,7 @@ from authentik.providers.oauth2.views.introspection import TokenIntrospectionVie
from authentik.providers.oauth2.views.jwks import JWKSView
from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.oauth2.views.token import TokenView
from authentik.providers.oauth2.views.token_revoke import TokenRevokeView
from authentik.providers.oauth2.views.userinfo import UserInfoView
urlpatterns = [
@ -29,9 +30,14 @@ urlpatterns = [
csrf_exempt(TokenIntrospectionView.as_view()),
name="token-introspection",
),
path(
"revoke/",
csrf_exempt(TokenRevokeView.as_view()),
name="token-revoke",
),
path(
"<slug:application_slug>/end-session/",
RedirectView.as_view(pattern_name="authentik_core:if-session-end"),
RedirectView.as_view(pattern_name="authentik_core:if-session-end", query_string=True),
name="end-session",
),
path("<slug:application_slug>/jwks/", JWKSView.as_view(), name="jwks"),

View File

@ -10,9 +10,10 @@ from django.http.response import HttpResponseRedirect
from django.utils.cache import patch_vary_headers
from structlog.stdlib import get_logger
from authentik.core.middleware import KEY_USER
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.errors import BearerTokenError
from authentik.providers.oauth2.models import RefreshToken
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
LOGGER = get_logger()
@ -165,13 +166,30 @@ def protected_resource_view(scopes: list[str]):
] = f'error="{error.code}", error_description="{error.description}"'
return response
kwargs["token"] = token
return view(request, *args, **kwargs)
response = view(request, *args, **kwargs)
setattr(response, "ak_context", {})
response.ak_context[KEY_USER] = token.user.username
return response
return view_wrapper
return wrapper
def authenticate_provider(request: HttpRequest) -> Optional[OAuth2Provider]:
"""Attempt to authenticate via Basic auth of client_id:client_secret"""
client_id, client_secret = extract_client_auth(request)
if client_id == client_secret == "":
return None
provider: Optional[OAuth2Provider] = OAuth2Provider.objects.filter(client_id=client_id).first()
if not provider:
return None
if client_id != provider.client_id or client_secret != provider.client_secret:
LOGGER.debug("(basic) Provider for basic auth does not exist")
return None
return provider
class HttpResponseRedirectScheme(HttpResponseRedirect):
"""HTTP Response to redirect, can be to a non-http scheme"""

View File

@ -2,7 +2,7 @@
from dataclasses import dataclass, field
from datetime import timedelta
from re import error as RegexError
from re import escape, fullmatch
from re import fullmatch
from typing import Optional
from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
from uuid import uuid4
@ -55,6 +55,7 @@ from authentik.providers.oauth2.models import (
OAuth2Provider,
ResponseMode,
ResponseTypes,
ScopeMapping,
)
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
from authentik.providers.oauth2.views.userinfo import UserInfoView
@ -181,7 +182,7 @@ class OAuthAuthorizationParams:
if self.provider.redirect_uris == "":
LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri)
self.provider.redirect_uris = escape(self.redirect_uri)
self.provider.redirect_uris = self.redirect_uri
self.provider.save()
allowed_redirect_urls = self.provider.redirect_uris.split()
@ -194,14 +195,20 @@ class OAuthAuthorizationParams:
try:
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri",
"Invalid redirect uri (regex comparison)",
redirect_uri=self.redirect_uri,
expected=allowed_redirect_urls,
)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
except RegexError as exc:
LOGGER.warning("Invalid regular expression configured", exc=exc)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
if not any(x == self.redirect_uri for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri (strict comparison)",
redirect_uri=self.redirect_uri,
expected=allowed_redirect_urls,
)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
if self.request:
raise AuthorizeError(
self.redirect_uri, "request_not_supported", self.grant_type, self.state
@ -209,6 +216,16 @@ class OAuthAuthorizationParams:
def check_scope(self):
"""Ensure openid scope is set in Hybrid flows, or when requesting an id_token"""
if len(self.scope) == 0:
default_scope_names = set(
ScopeMapping.objects.filter(provider__in=[self.provider]).values_list(
"scope_name", flat=True
)
)
self.scope = default_scope_names
LOGGER.info(
"No scopes requested, defaulting to all configured scopes", scopes=self.scope
)
if SCOPE_OPENID not in self.scope and (
self.grant_type == GrantTypes.HYBRID
or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN]
@ -234,11 +251,8 @@ class OAuthAuthorizationParams:
def check_code_challenge(self):
"""PKCE validation of the transformation method."""
if self.code_challenge:
if not (self.code_challenge_method in ["plain", "S256"]):
raise AuthorizeError(
self.redirect_uri, "invalid_request", self.grant_type, self.state
)
if self.code_challenge and self.code_challenge_method not in ["plain", "S256"]:
raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state)
def create_code(self, request: HttpRequest) -> AuthorizationCode:
"""Create an AuthorizationCode object for the request"""
@ -459,7 +473,6 @@ class OAuthFulfillmentStage(StageView):
def create_response_uri(self) -> str:
"""Create a final Response URI the user is redirected to."""
uri = urlsplit(self.params.redirect_uri)
query_params = parse_qs(uri.query)
try:
code = None
@ -472,6 +485,7 @@ class OAuthFulfillmentStage(StageView):
code.save(force_insert=True)
if self.params.response_mode == ResponseMode.QUERY:
query_params = parse_qs(uri.query)
query_params["code"] = code.code
query_params["state"] = [str(self.params.state) if self.params.state else ""]
@ -488,7 +502,12 @@ class OAuthFulfillmentStage(StageView):
return urlunsplit(uri)
if self.params.response_mode == ResponseMode.FORM_POST:
post_params = self.create_implicit_response(code)
post_params = {}
if self.params.grant_type in [GrantTypes.AUTHORIZATION_CODE]:
post_params["code"] = code.code
post_params["state"] = [str(self.params.state) if self.params.state else ""]
else:
post_params = self.create_implicit_response(code)
uri = uri._replace(query=urlencode(post_params, doseq=True))

View File

@ -7,11 +7,7 @@ from structlog.stdlib import get_logger
from authentik.providers.oauth2.errors import TokenIntrospectionError
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.utils import (
TokenResponse,
extract_access_token,
extract_client_auth,
)
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
LOGGER = get_logger()
@ -21,8 +17,8 @@ class TokenIntrospectionParams:
"""Parameters for Token Introspection"""
token: RefreshToken
provider: OAuth2Provider
provider: OAuth2Provider = field(init=False)
id_token: IDToken = field(init=False)
def __post_init__(self):
@ -30,7 +26,6 @@ class TokenIntrospectionParams:
LOGGER.debug("Token is not valid")
raise TokenIntrospectionError()
self.provider = self.token.provider
self.id_token = self.token.id_token
if not self.token.id_token:
@ -40,30 +35,6 @@ class TokenIntrospectionParams:
)
raise TokenIntrospectionError()
def authenticate_basic(self, request: HttpRequest) -> bool:
"""Attempt to authenticate via Basic auth of client_id:client_secret"""
client_id, client_secret = extract_client_auth(request)
if client_id == client_secret == "":
return False
if client_id != self.provider.client_id or client_secret != self.provider.client_secret:
LOGGER.debug("(basic) Provider for basic auth does not exist")
raise TokenIntrospectionError()
return True
def authenticate_bearer(self, request: HttpRequest) -> bool:
"""Attempt to authenticate via token sent as bearer header"""
body_token = extract_access_token(request)
if not body_token:
return False
tokens = RefreshToken.objects.filter(access_token=body_token).select_related("provider")
if not tokens.exists():
LOGGER.debug("(bearer) Token does not exist")
raise TokenIntrospectionError()
if tokens.first().provider != self.provider:
LOGGER.debug("(bearer) Token providers don't match")
raise TokenIntrospectionError()
return True
@staticmethod
def from_request(request: HttpRequest) -> "TokenIntrospectionParams":
"""Extract required Parameters from HTTP Request"""
@ -75,19 +46,17 @@ class TokenIntrospectionParams:
LOGGER.debug("token_type_hint has invalid value", value=token_type_hint)
raise TokenIntrospectionError()
provider = authenticate_provider(request)
if not provider:
raise TokenIntrospectionError
try:
token: RefreshToken = RefreshToken.objects.select_related("provider").get(
**token_filter
)
token: RefreshToken = RefreshToken.objects.get(provider=provider, **token_filter)
except RefreshToken.DoesNotExist:
LOGGER.debug("Token does not exist", token=raw_token)
raise TokenIntrospectionError()
params = TokenIntrospectionParams(token=token)
if not any([params.authenticate_basic(request), params.authenticate_bearer(request)]):
LOGGER.warning("Not authenticated")
raise TokenIntrospectionError()
return params
return TokenIntrospectionParams(token=token, provider=provider)
class TokenIntrospectionView(View):

View File

@ -58,6 +58,9 @@ class ProviderInfoView(View):
"introspection_endpoint": self.request.build_absolute_uri(
reverse("authentik_providers_oauth2:token-introspection")
),
"revocation_endpoint": self.request.build_absolute_uri(
reverse("authentik_providers_oauth2:token-revoke")
),
"response_types_supported": [
ResponseTypes.CODE,
ResponseTypes.ID_TOKEN,

View File

@ -21,7 +21,6 @@ from authentik.core.models import (
TokenIntents,
User,
)
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
@ -38,7 +37,6 @@ from authentik.providers.oauth2.errors import TokenError, UserAuthError
from authentik.providers.oauth2.models import (
AuthorizationCode,
ClientTypes,
JWTAlgorithms,
OAuth2Provider,
RefreshToken,
)
@ -89,7 +87,7 @@ class TokenParams:
provider=provider,
client_id=client_id,
client_secret=client_secret,
redirect_uri=request.POST.get("redirect_uri", "").lower(),
redirect_uri=request.POST.get("redirect_uri", ""),
grant_type=request.POST.get("grant_type", ""),
state=request.POST.get("state", ""),
scope=request.POST.get("scope", "").split(),
@ -154,7 +152,7 @@ class TokenParams:
try:
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri",
"Invalid redirect uri (regex comparison)",
redirect_uri=self.redirect_uri,
expected=allowed_redirect_urls,
)
@ -167,13 +165,19 @@ class TokenParams:
).from_http(request)
raise TokenError("invalid_client")
except RegexError as exc:
LOGGER.warning("Invalid regular expression configured", exc=exc)
Event.new(
EventAction.CONFIGURATION_ERROR,
message="Invalid redirect_uri RegEx configured",
provider=self.provider,
).from_http(request)
raise TokenError("invalid_client")
LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
if not any(x == self.redirect_uri for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri (strict comparison)",
redirect_uri=self.redirect_uri,
expected=allowed_redirect_urls,
)
Event.new(
EventAction.CONFIGURATION_ERROR,
message="Invalid redirect_uri configured",
provider=self.provider,
).from_http(request)
raise TokenError("invalid_client")
try:
self.authorization_code = AuthorizationCode.objects.get(code=raw_code)
@ -286,26 +290,6 @@ class TokenParams:
token = None
# TODO: Remove in 2022.7, deprecated field `verification_keys``
for cert in self.provider.verification_keys.all():
LOGGER.debug("verifying jwt with key", key=cert.name)
cert: CertificateKeyPair
public_key = cert.certificate.public_key()
if cert.private_key:
public_key = cert.private_key.public_key()
try:
token = decode(
assertion,
public_key,
algorithms=[JWTAlgorithms.RS256, JWTAlgorithms.ES256],
options={
"verify_aud": False,
},
)
except (PyJWTError, ValueError, TypeError) as exc:
LOGGER.warning("failed to validate jwt", exc=exc)
# TODO: End remove block
source: Optional[OAuthSource] = None
parsed_key: Optional[PyJWK] = None
for source in self.provider.jwks_sources.all():
@ -345,7 +329,7 @@ class TokenParams:
raise TokenError("invalid_grant")
self.__check_policy_access(app, request, oauth_jwt=token)
self.__create_user_from_jwt(token, app)
self.__create_user_from_jwt(token, app, source)
method_args = {
"jwt": token,
@ -361,7 +345,7 @@ class TokenParams:
PLAN_CONTEXT_APPLICATION=app,
).from_http(request, user=self.user)
def __create_user_from_jwt(self, token: dict[str, Any], app: Application):
def __create_user_from_jwt(self, token: dict[str, Any], app: Application, source: OAuthSource):
"""Create user from JWT"""
exp = token.get("exp")
self.user, created = User.objects.update_or_create(
@ -372,6 +356,7 @@ class TokenParams:
},
"last_login": now(),
"name": f"Autogenerated user from application {app.name} (client credentials JWT)",
"path": source.get_user_path(),
},
)
if created and exp:

View File

@ -0,0 +1,66 @@
"""Token revocation endpoint"""
from dataclasses import dataclass
from django.http import Http404, HttpRequest, HttpResponse
from django.views import View
from structlog.stdlib import get_logger
from authentik.providers.oauth2.errors import TokenRevocationError
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
LOGGER = get_logger()
@dataclass
class TokenRevocationParams:
"""Parameters for Token Revocation"""
token: RefreshToken
provider: OAuth2Provider
@staticmethod
def from_request(request: HttpRequest) -> "TokenRevocationParams":
"""Extract required Parameters from HTTP Request"""
raw_token = request.POST.get("token")
token_type_hint = request.POST.get("token_type_hint", "access_token")
token_filter = {token_type_hint: raw_token}
if token_type_hint not in ["access_token", "refresh_token"]:
LOGGER.debug("token_type_hint has invalid value", value=token_type_hint)
raise TokenRevocationError("unsupported_token_type")
provider = authenticate_provider(request)
if not provider:
raise TokenRevocationError("invalid_client")
try:
token: RefreshToken = RefreshToken.objects.get(provider=provider, **token_filter)
except RefreshToken.DoesNotExist:
LOGGER.debug("Token does not exist", token=raw_token)
raise Http404
return TokenRevocationParams(token=token, provider=provider)
class TokenRevokeView(View):
"""Token revoke endpoint
https://datatracker.ietf.org/doc/html/rfc7009"""
token: RefreshToken
params: TokenRevocationParams
provider: OAuth2Provider
def post(self, request: HttpRequest) -> HttpResponse:
"""Revocation handler"""
try:
self.params = TokenRevocationParams.from_request(request)
self.params.token.delete()
return TokenResponse(data={}, status=200)
except TokenRevocationError as exc:
return TokenResponse(exc.create_dict(), status=401)
except Http404:
# Token not found should return a HTTP 200 according to the specs
return TokenResponse(data={}, status=200)

View File

@ -4,12 +4,15 @@ from typing import Any, Optional
from deepmerge import always_merger
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.utils.translation import gettext_lazy as _
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import PermissionDict
from authentik.providers.oauth2.constants import (
SCOPE_AUTHENTIK_API,
SCOPE_GITHUB_ORG_READ,
SCOPE_GITHUB_USER,
SCOPE_GITHUB_USER_EMAIL,
@ -27,23 +30,27 @@ class UserInfoView(View):
token: Optional[RefreshToken]
def get_scope_descriptions(self, scopes: list[str]) -> list[dict[str, str]]:
def get_scope_descriptions(self, scopes: list[str]) -> list[PermissionDict]:
"""Get a list of all Scopes's descriptions"""
scope_descriptions = []
for scope in ScopeMapping.objects.filter(scope_name__in=scopes).order_by("scope_name"):
if scope.description != "":
scope_descriptions.append({"id": scope.scope_name, "name": scope.description})
if scope.description == "":
continue
scope_descriptions.append(PermissionDict(id=scope.scope_name, name=scope.description))
# GitHub Compatibility Scopes are handled differently, since they required custom paths
# Hence they don't exist as Scope objects
github_scope_map = {
SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"),
SCOPE_GITHUB_USER_READ: ("GitHub Compatibility: Access your User Information"),
SCOPE_GITHUB_USER_EMAIL: ("GitHub Compatibility: Access you Email addresses"),
SCOPE_GITHUB_ORG_READ: ("GitHub Compatibility: Access your Groups"),
special_scope_map = {
SCOPE_GITHUB_USER: _("GitHub Compatibility: Access your User Information"),
SCOPE_GITHUB_USER_READ: _("GitHub Compatibility: Access your User Information"),
SCOPE_GITHUB_USER_EMAIL: _("GitHub Compatibility: Access you Email addresses"),
SCOPE_GITHUB_ORG_READ: _("GitHub Compatibility: Access your Groups"),
SCOPE_AUTHENTIK_API: _("authentik API Access on behalf of your user"),
}
for scope in scopes:
if scope in github_scope_map:
scope_descriptions.append({"id": scope, "name": github_scope_map[scope]})
if scope in special_scope_map:
scope_descriptions.append(
PermissionDict(id=scope, name=str(special_scope_map[scope]))
)
return scope_descriptions
def get_claims(self, token: RefreshToken) -> dict[str, Any]:

View File

@ -11,11 +11,6 @@ from rest_framework.serializers import Serializer
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import DomainlessURLValidator
from authentik.outposts.models import OutpostModel
from authentik.providers.oauth2.constants import (
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
)
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
SCOPE_AK_PROXY = "ak_proxy"
@ -125,11 +120,11 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
self.client_type = ClientTypes.CONFIDENTIAL
self.signing_key = None
scopes = ScopeMapping.objects.filter(
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_PROFILE,
SCOPE_OPENID_EMAIL,
SCOPE_AK_PROXY,
managed__in=[
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-profile",
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/proxy/scope-proxy",
]
)
self.property_mappings.add(*list(scopes))

View File

@ -2,6 +2,7 @@
from xml.etree.ElementTree import ParseError # nosec
from defusedxml.ElementTree import fromstring
from django.http import HttpRequest
from django.http.response import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
@ -44,14 +45,58 @@ LOGGER = get_logger()
class SAMLProviderSerializer(ProviderSerializer):
"""SAMLProvider Serializer"""
metadata_download_url = SerializerMethodField()
url_download_metadata = SerializerMethodField()
def get_metadata_download_url(self, instance: SAMLProvider) -> str:
url_sso_post = SerializerMethodField()
url_sso_redirect = SerializerMethodField()
url_sso_init = SerializerMethodField()
def get_url_download_metadata(self, instance: SAMLProvider) -> str:
"""Get metadata download URL"""
return (
request: HttpRequest = self._context["request"]._request
return request.build_absolute_uri(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": instance.pk}) + "?download"
)
def get_url_sso_post(self, instance: SAMLProvider) -> str:
"""Get SSO Post URL"""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:sso-post",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
return "-"
def get_url_sso_redirect(self, instance: SAMLProvider) -> str:
"""Get SSO Redirect URL"""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:sso-redirect",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
return "-"
def get_url_sso_init(self, instance: SAMLProvider) -> str:
"""Get SSO IDP-Initiated URL"""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:sso-init",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
return "-"
class Meta:
model = SAMLProvider
@ -69,7 +114,10 @@ class SAMLProviderSerializer(ProviderSerializer):
"signing_kp",
"verification_kp",
"sp_binding",
"metadata_download_url",
"url_download_metadata",
"url_sso_post",
"url_sso_redirect",
"url_sso_init",
]

View File

@ -1,6 +1,7 @@
"""authentik core celery"""
import os
from logging.config import dictConfig
from typing import Callable
from celery import Celery
from celery.signals import (
@ -76,23 +77,28 @@ def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
@worker_ready.connect
def worker_ready_hook(*args, **kwargs):
"""Run certain tasks on worker start"""
def _get_startup_tasks() -> list[Callable]:
"""Get all tasks to be run on startup"""
from authentik.admin.tasks import clear_update_notifications
from authentik.managed.tasks import managed_reconcile
from authentik.outposts.tasks import outpost_controller_all, outpost_local_connection
from authentik.providers.proxy.tasks import proxy_set_defaults
tasks = [
return [
clear_update_notifications,
outpost_local_connection,
outpost_controller_all,
proxy_set_defaults,
managed_reconcile,
]
@worker_ready.connect
def worker_ready_hook(*args, **kwargs):
"""Run certain tasks on worker start"""
LOGGER.info("Dispatching startup tasks...")
for task in tasks:
for task in _get_startup_tasks():
try:
task.delay()
except ProgrammingError as exc:

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