Compare commits

..

91 Commits

Author SHA1 Message Date
3fa987f443 start config file watch
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 21:32:33 +01:00
5ea9595c9c internal: fix cache-control header
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#4525
2023-01-25 21:18:20 +01:00
1b6f920265 web/flows: improve handling of flow info
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 15:01:08 +01:00
3bf8c915d5 web/flows: update flow background image
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 15:00:56 +01:00
1cc578be66 website: update comparison
closes #4088

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:49:09 +01:00
36f8f8bae5 stages/prompt: fix mismatched name field in migration
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:46:40 +01:00
68058fb2ae stages/authenticator_validate: fix error with passwordless webauthn login, improve tests
closes #4527

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-25 14:45:00 +01:00
0abbe8288e website: update comparison
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#4088
2023-01-25 11:39:58 +01:00
a9de9101ca web: bump @sentry/browser from 7.32.1 to 7.33.0 in /web (#4516)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.32.1 to 7.33.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.32.1...7.33.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:34:41 +01:00
19ed9dc5e9 web: bump @lingui/macro from 3.16.0 to 3.16.1 in /web (#4518)
Bumps [@lingui/macro](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [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.16.0...v3.16.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:34:28 +01:00
b5ae712f35 website: bump ua-parser-js from 0.7.32 to 0.7.33 in /website (#4526)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.32 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.32...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:25:38 +01:00
b1c01b53e7 web: bump @lingui/cli from 3.16.0 to 3.16.1 in /web (#4522)
Bumps [@lingui/cli](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [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.16.0...v3.16.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:24:11 +01:00
5b31e47573 core: bump goauthentik.io/api/v3 from 3.2023012.1 to 3.2023012.2 (#4523)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2023012.1 to 3.2023012.2.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2023012.1...v3.2023012.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:50 +01:00
d2a9a294f2 web: bump pyright from 1.1.290 to 1.1.291 in /web (#4519)
Bumps [pyright](https://github.com/Microsoft/pyright/tree/HEAD/packages/pyright) from 1.1.290 to 1.1.291.
- [Release notes](https://github.com/Microsoft/pyright/releases)
- [Commits](https://github.com/Microsoft/pyright/commits/1.1.291/packages/pyright)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:38 +01:00
32c7c58518 web: bump @sentry/tracing from 7.32.1 to 7.33.0 in /web (#4517)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.32.1 to 7.33.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.32.1...7.33.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:23:28 +01:00
349c6e50c2 web: bump @lingui/detect-locale from 3.16.0 to 3.16.1 in /web (#4521)
Bumps [@lingui/detect-locale](https://github.com/lingui/js-lingui) from 3.16.0 to 3.16.1.
- [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.16.0...v3.16.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:22:50 +01:00
2e8027fa04 core: bump coverage from 7.0.5 to 7.1.0 (#4524)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.0.5 to 7.1.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.0.5...7.1.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 11:22:37 +01:00
0bc1b33663 website/blog: fix mixed up images
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:46:15 +01:00
8564f9ef87 website: hardcode url for disqus comments
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:26:04 +01:00
7cfd84d8f0 website/blog: update blog title
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 19:03:05 +01:00
f2e40ec7e3 website: publish new blog post
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 18:59:54 +01:00
1f1255a829 web: bump API Client version (#4508)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
2023-01-24 13:45:05 +01:00
53b65a9d1a stages/prompt: field name (#4497)
* add prompt field name

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

* remove numerical prefix

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

* fix missing name

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

* use text field

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

* add description label

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

* add migrate blueprint to remove old stages

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

* add task to remove unretrievable blueprints

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

* lint

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

* fix blueprint test paths

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

* fix tests

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

* actually fix tests

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

* fix tests even more

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

* fix fixtures

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 12:23:22 +01:00
9437e2d3ab website: add disqus to blog posts
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 12:14:59 +01:00
6a7b78abc2 web: bump @sentry/browser from 7.31.1 to 7.32.1 in /web (#4498)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 7.31.1 to 7.32.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.31.1...7.32.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 11:05:12 +01:00
9c24e5195b core: bump goauthentik.io/api/v3 from 3.2023010.1 to 3.2023012.1 (#4500)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2023010.1 to 3.2023012.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2023010.1...v3.2023012.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:56:29 +01:00
306b046b9a web: bump @sentry/tracing from 7.31.1 to 7.32.1 in /web (#4499)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 7.31.1 to 7.32.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.31.1...7.32.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:56:18 +01:00
10b50c5845 web: bump @typescript-eslint/parser from 5.48.2 to 5.49.0 in /web (#4501)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.48.2 to 5.49.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.49.0/packages/parser)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:52:08 +01:00
3912a57df2 core: bump sentry-sdk from 1.13.0 to 1.14.0 (#4502)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.13.0 to 1.14.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.13.0...1.14.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:51:38 +01:00
08142d393f web: bump @typescript-eslint/eslint-plugin from 5.48.2 to 5.49.0 in /web (#4503)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.48.2 to 5.49.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.49.0/packages/eslint-plugin)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:48:19 +01:00
0f7af256d6 web: bump @types/codemirror from 5.60.6 to 5.60.7 in /web (#4504)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 5.60.6 to 5.60.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:48:00 +01:00
16076cc46f outposts: fallback to ghcr
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-24 10:47:30 +01:00
8aa16e66e4 core: bump selenium from 4.7.2 to 4.8.0 (#4505)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.7.2 to 4.8.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits/selenium-4.8.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:47:10 +01:00
a9b32e2f97 providers/ldap: add unbind flow execution (#4484)
add unbind flow execution

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 20:36:30 +01:00
b2d272bf6f api: fix lint
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 20:19:03 +01:00
31ef6fb6a6 core: delete session when user is set to inactive
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 16:24:30 +01:00
c9c059a008 api: ensure user is active when authenticating
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 16:24:30 +01:00
a5e84b5482 web: bump API Client version (#4492)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
2023-01-23 15:18:33 +00:00
7c697e09f3 Merge branch 'version-2023.1' 2023-01-23 14:39:59 +01:00
b99afd82b2 stages/user_write: fix migration setting wrong value, fix form
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 14:38:26 +01:00
9397598376 release: 2023.1.2 2023-01-23 14:25:55 +01:00
91ffe4e7f9 stages/user_write: fix migration setting wrong value, fix form
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 14:05:41 +01:00
430a207865 release: 2023.1.1 2023-01-23 11:34:58 +01:00
894873b373 core: compile backend translations (#4489)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2023-01-23 11:00:35 +01:00
1ce2a1b846 stages/email: update tests
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-23 10:43:49 +01:00
4731ccfafe stages/email: fix a typo in email template (#4485)
fix a typo in main content

Signed-off-by: Loan J <joliveau.loan@gmail.com>

Signed-off-by: Loan J <joliveau.loan@gmail.com>
2023-01-23 10:22:49 +01:00
3e9c28d0a4 web: bump @babel/plugin-proposal-decorators from 7.20.7 to 7.20.13 in /web (#4486)
web: 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.20.7 to 7.20.13.
- [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.20.13/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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 10:21:46 +01:00
12d4394d73 web: bump @rollup/plugin-commonjs from 24.0.0 to 24.0.1 in /web (#4487)
Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs) from 24.0.0 to 24.0.1.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/commonjs-v24.0.1/packages/commonjs)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 10:21:24 +01:00
b872e7072d core: bump paramiko from 2.12.0 to 3.0.0 (#4488)
Bumps [paramiko](https://github.com/paramiko/paramiko) from 2.12.0 to 3.0.0.
- [Release notes](https://github.com/paramiko/paramiko/releases)
- [Changelog](https://github.com/paramiko/paramiko/blob/main/NEWS)
- [Commits](https://github.com/paramiko/paramiko/compare/2.12.0...3.0.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 10:20:51 +01:00
b0ea657b18 ci: fix incorrect action for geoip secrets for release pipeline
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-22 19:19:41 +01:00
a5f26b2ce0 ci: use bot token to create release
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-22 19:09:49 +01:00
c1b9b5c5e2 stages/authenticator_totp: url quote TOTP issuer instead of slugifying (#4482)
* Fix TOTP issuer mangling

* Fix OTP issuer mangling

* sort imports

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-01-22 16:37:47 +00:00
b288393cd4 stages/invitation: handle incorrectly formatted token
closes #4481

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-22 00:03:39 +01:00
767ffc09d0 web/admin: fix plex source authorization flow not being shown correctly
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-21 14:55:13 +01:00
446dc0a17b website/docs: prepare 2023.1.1
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-20 14:25:20 +01:00
c85474ec37 root: update supported versions
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-20 14:23:21 +01:00
3a59b75f4a website/docs: update ldap provider docs
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-20 11:46:57 +01:00
8deac81364 outposts/ldap: fix queries filtering objectClass with non-lowercase values
closes #2756

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-20 11:42:23 +01:00
98485c528e ci: build beta for amd64 and arm64 (#4468)
* ci: build for arm64, but independently

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

* add notice to beta

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 21:41:56 +01:00
1a5b626f96 root: match warning exclusions in pytest
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 20:34:51 +01:00
5736a1542c stages/authenticator_sms: fix code not being sent when phone_number is in context
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 20:19:23 +01:00
43854dc828 outposts/proxy: fix panic due to IsSet misbehaving
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 18:22:55 +01:00
64af78110a ci: bump golangcilint timeout
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 18:17:18 +01:00
59be3c7746 website/docs: add docs for validating phone numbers before SMS enrollment
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 17:57:52 +01:00
9e30f01fce web/admin: don't enable execution logging by default
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 17:57:34 +01:00
fc8fe5317a stages: always use get_pending_user instead of getting context user
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 17:57:21 +01:00
92090ced9f web/elements: fix pf-c-switch not rendering correctly in pure tables
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 15:47:11 +01:00
ce47d4cf39 web/admin: link group of notification rule
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 15:46:37 +01:00
c11367553e providers/proxy: fix issuer for embedded outpost (#4480)
fix issuer for embedded outpost

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 15:39:30 +01:00
c61529e4d4 sources/ldap: add e2e LDAP source tests (#4462)
* start adding more LDAP source tests

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

* improve healthcheck

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

* try local webdriver

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

* add full samba tests

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

* fix locale types

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 15:03:56 +01:00
8709f3300c tests: fix OIDC tests running twice
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 12:28:51 +01:00
e78bc1b32f web/admin: improve display of system task exception
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 12:13:11 +01:00
89c4a7b4a4 web/admin: improve display of rule severity
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 12:09:53 +01:00
9701907b82 web: bump @lingui/macro from 3.15.0 to 3.16.0 in /web (#4473) 2023-01-19 10:44:50 +01:00
7ac73bfcf9 web: bump @lingui/cli from 3.15.0 to 3.16.0 in /web (#4478) 2023-01-19 10:44:25 +01:00
1423d5d45b web: bump @lingui/core from 3.15.0 to 3.16.0 in /web (#4474) 2023-01-19 10:19:42 +01:00
768ff67e8c web: bump core-js from 3.27.1 to 3.27.2 in /web (#4475) 2023-01-19 10:15:12 +01:00
b13deefd91 web: bump chart.js from 4.1.2 to 4.2.0 in /web (#4476) 2023-01-19 10:14:50 +01:00
69e445211e core: bump goauthentik.io/api/v3 from 3.2022122.8 to 3.2023010.1 (#4479) 2023-01-19 10:14:28 +01:00
ada8fc2a55 web: bump @lingui/detect-locale from 3.15.0 to 3.16.0 in /web (#4477) 2023-01-19 10:14:14 +01:00
d9cc45f9ce web/elements: fix SearchSelect not working on safari
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-19 00:08:23 +01:00
515a402db7 web/flows: fix flow executor background overlay in safari
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 20:43:19 +01:00
813f70b806 blueprints: fix OOB email field overwriting user settings email field
closes #4317

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 19:40:47 +01:00
a302a72379 crypto: fallback when no SAN values are given
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 19:40:24 +01:00
e390f5b2d1 providers/oauth2: more x5c and ecdsa x/y tests (#4463)
* add option to exclude x5*

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

#4082

* cleanup jwks, add flaky test

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

* add workaround based on https://github.com/jpadilla/pyjwt/issues/709

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

* don't rstrip hashes

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

* keycloak seems to strip equals

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

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 18:11:36 +00:00
f09305a444 root: don't specify bind IP in compose file
closes #4470

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 19:03:36 +01:00
60189ce9ca add tests to prevent empty SAN
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 18:59:10 +01:00
fdc445e6a1 ensure we don't generate an empty SAN certificate
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 18:44:41 +01:00
e3f8afcf80 web: bump API Client version (#4469)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
2023-01-18 16:27:20 +00:00
9e2e8132a6 Merge branch 'version-2023.1' 2023-01-18 17:14:10 +01:00
26f9bbeefa website/docs: add 2023.1 to sidebar
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-01-18 16:36:03 +01:00
157 changed files with 2612 additions and 1087 deletions

View File

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

View File

@ -38,6 +38,14 @@ runs:
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
```
For arm64, use these values:
```shell
AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=${{ inputs.tag }}-arm64
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
```
Afterwards, run the upgrade commands from the latest release notes.
</details>
<details>
@ -54,6 +62,17 @@ runs:
tag: ${{ inputs.tag }}
```
For arm64, use these values:
```yaml
authentik:
outposts:
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
image:
repository: ghcr.io/goauthentik/dev-server
tag: ${{ inputs.tag }}-arm64
```
Afterwards, run the upgrade commands from the latest release notes.
</details>
edit-mode: replace

View File

@ -17,6 +17,9 @@ outputs:
sha:
description: "sha"
value: ${{ steps.ev.outputs.sha }}
shortHash:
description: "shortHash"
value: ${{ steps.ev.outputs.shortHash }}
version:
description: "version"
value: ${{ steps.ev.outputs.version }}
@ -53,6 +56,7 @@ runs:
print("branchNameContainer=%s" % safe_branch_name, file=_output)
print("timestamp=%s" % int(time()), file=_output)
print("sha=%s" % os.environ["GITHUB_SHA"], file=_output)
print("shortHash=%s" % os.environ["GITHUB_SHA"][:7], file=_output)
print("shouldBuild=%s" % should_build, file=_output)
print("version=%s" % version, file=_output)
print("versionFamily=%s" % version_family, file=_output)

View File

@ -118,13 +118,13 @@ jobs:
- name: proxy
glob: tests/e2e/test_provider_proxy*
- name: oauth
glob: tests/e2e/test_provider_oauth2_!(oidc) tests/e2e/test_source_oauth*
glob: tests/e2e/test_provider_oauth2* tests/e2e/test_source_oauth*
- name: oauth-oidc
glob: tests/e2e/test_provider_oauth2_oidc*
glob: tests/e2e/test_provider_oidc*
- name: saml
glob: tests/e2e/test_provider_saml* tests/e2e/test_source_saml*
- name: ldap
glob: tests/e2e/test_provider_ldap*
glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap*
- name: flows
glob: tests/e2e/test_flows*
steps:
@ -148,7 +148,6 @@ jobs:
npm run build
- name: run e2e
run: |
shopt -s extglob
poetry run coverage run manage.py test ${{ matrix.job.glob }}
poetry run coverage xml
- if: ${{ always() }}
@ -170,11 +169,6 @@ jobs:
needs: ci-core-mark
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
arch:
- 'linux/amd64'
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
@ -202,14 +196,49 @@ jobs:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
platforms: ${{ matrix.arch }}
- name: Comment on PR
if: github.event_name == 'pull_request'
continue-on-error: true
uses: ./.github/actions/comment-pr-instructions
with:
tag: gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
tag: gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }}
build-arm64:
needs: ci-core-mark
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- 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 }}
- name: Login to Container Registry
uses: docker/login-action@v2
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@v3
with:
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-arm64
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }}-arm64
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
platforms: linux/arm64

View File

@ -28,6 +28,8 @@ jobs:
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
args: --timeout 5000s
test-unittest:
runs-on: ubuntu-latest
steps:
@ -86,7 +88,6 @@ jobs:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchNameContainer }}
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }}
file: ${{ matrix.type }}.Dockerfile
build-args: |

View File

@ -31,7 +31,7 @@ jobs:
uses: docker/build-push-action@v3
with:
push: ${{ github.event_name == 'release' }}
secrets:
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
tags: |
@ -88,9 +88,6 @@ jobs:
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
secrets: |
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
build-args: |
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
build-outpost-binary:

View File

@ -26,14 +26,14 @@ jobs:
id: get_version
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
script: |
return context.payload.ref.replace(/\/refs\/tags\/version\//, '');
- name: Create Release
id: create_release
uses: actions/create-release@v1.1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.get_version.outputs.result }}

View File

@ -19,6 +19,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
- name: Setup authentik env
uses: ./.github/actions/setup
- name: run compile
@ -27,7 +29,7 @@ jobs:
uses: peter-evans/create-pull-request@v4
id: cpr
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.BOT_GITHUB_TOKEN }}
branch: compile-backend-translation
commit-message: "core: compile backend translations"
title: "core: compile backend translations"

View File

@ -6,8 +6,8 @@ Authentik takes security very seriously. We follow the rules of [responsible dis
| Version | Supported |
| --------- | ------------------ |
| 2022.11.x | :white_check_mark: |
| 2022.12.x | :white_check_mark: |
| 2023.1.x | :white_check_mark: |
## Reporting a Vulnerability

View File

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

View File

@ -31,6 +31,16 @@ def validate_auth(header: bytes) -> Optional[str]:
def bearer_auth(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`"""
user = auth_user_lookup(raw_header)
if not user:
return None
if not user.is_active:
raise AuthenticationFailed("Token invalid/expired")
return user
def auth_user_lookup(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`"""
from authentik.providers.oauth2.models import RefreshToken

View File

@ -3,13 +3,12 @@ from base64 import b64encode
from django.conf import settings
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from rest_framework.exceptions import AuthenticationFailed
from authentik.api.authentication import bearer_auth
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents
from authentik.core.tests.utils import create_test_flow
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
@ -36,9 +35,18 @@ class TestAPIAuth(TestCase):
def test_bearer_valid(self):
"""Test valid token"""
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=get_anonymous_user())
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=create_test_admin_user())
self.assertEqual(bearer_auth(f"Bearer {token.key}".encode()), token.user)
def test_bearer_valid_deactivated(self):
"""Test valid token"""
user = create_test_admin_user()
user.is_active = False
user.save()
token = Token.objects.create(intent=TokenIntents.INTENT_API, user=user)
with self.assertRaises(AuthenticationFailed):
bearer_auth(f"Bearer {token.key}".encode())
def test_managed_outpost(self):
"""Test managed outpost"""
with self.assertRaises(AuthenticationFailed):
@ -56,7 +64,7 @@ class TestAPIAuth(TestCase):
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
)
refresh = RefreshToken.objects.create(
user=get_anonymous_user(),
user=create_test_admin_user(),
provider=provider,
refresh_token=generate_id(),
_scope=SCOPE_AUTHENTIK_API,
@ -69,7 +77,7 @@ class TestAPIAuth(TestCase):
name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
)
refresh = RefreshToken.objects.create(
user=get_anonymous_user(),
user=create_test_admin_user(),
provider=provider,
refresh_token=generate_id(),
_scope="",

View File

@ -57,9 +57,10 @@ class AuthentikBlueprintsConfig(ManagedAppConfig):
def reconcile_blueprints_discover(self):
"""Run blueprint discovery"""
from authentik.blueprints.v1.tasks import blueprints_discover
from authentik.blueprints.v1.tasks import blueprints_discover, clear_failed_blueprints
blueprints_discover.delay()
clear_failed_blueprints.delay()
def import_models(self):
super().import_models()

View File

@ -9,4 +9,9 @@ CELERY_BEAT_SCHEDULE = {
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
"blueprints_v1_cleanup": {
"task": "authentik.blueprints.v1.tasks.clear_failed_blueprints",
"schedule": crontab(minute=fqdn_rand("blueprints_v1_cleanup"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -4,6 +4,7 @@ entries:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt
attrs:
name: qwerweqrq
field_key: username
label: Username
type: username

View File

@ -13,7 +13,7 @@ from authentik.tenants.models import Tenant
class TestPackaged(TransactionTestCase):
"""Empty class, test methods are added dynamically"""
@apply_blueprint("default/90-default-tenant.yaml")
@apply_blueprint("default/default-tenant.yaml")
def test_decorator_static(self):
"""Test @apply_blueprint decorator"""
self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists())

View File

@ -262,15 +262,21 @@ class TestBlueprintsV1(TransactionTestCase):
with transaction_rollback():
# First stage fields
username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
)
password = Prompt.objects.create(
name=generate_id(),
field_key="password",
label="Password",
order=1,
type=FieldTypes.PASSWORD,
)
password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat",
label="Password (repeat)",
order=2,

View File

@ -3,3 +3,4 @@
LABEL_AUTHENTIK_SYSTEM = "blueprints.goauthentik.io/system"
LABEL_AUTHENTIK_INSTANTIATE = "blueprints.goauthentik.io/instantiate"
LABEL_AUTHENTIK_GENERATED = "blueprints.goauthentik.io/generated"
LABEL_AUTHENTIK_DESCRIPTION = "blueprints.goauthentik.io/description"

View File

@ -219,3 +219,14 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
finally:
if instance:
instance.save()
@CELERY_APP.task()
def clear_failed_blueprints():
"""Remove blueprints which couldn't be fetched"""
# Exclude OCI blueprints as those might be temporarily unavailable
for blueprint in BlueprintInstance.objects.exclude(path__startswith="oci://"):
try:
blueprint.retrieve()
except BlueprintRetrievalFailed:
blueprint.delete()

View File

@ -4,6 +4,8 @@ from json import loads
from typing import Any, Optional
from django.contrib.auth import update_session_auth_hash
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
from django.db.models.functions import ExtractHour
from django.db.models.query import QuerySet
from django.db.transaction import atomic
@ -57,6 +59,7 @@ from authentik.core.models import (
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
USER_PATH_SERVICE_ACCOUNT,
AuthenticatedSession,
Group,
Token,
TokenIntents,
@ -561,3 +564,14 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
}
)
def partial_update(self, request: Request, *args, **kwargs) -> Response:
response = super().partial_update(request, *args, **kwargs)
instance: User = self.get_object()
if not instance.is_active:
sessions = AuthenticatedSession.objects.filter(user=instance)
session_ids = sessions.values_list("session_key", flat=True)
cache.delete_many(f"{KEY_PREFIX}{session}" for session in session_ids)
sessions.delete()
LOGGER.debug("Deleted user's sessions", user=instance.username)
return response

View File

@ -1,10 +1,12 @@
"""Test Users API"""
from json import loads
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.core.models import AuthenticatedSession, 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.config import CONFIG
@ -257,3 +259,26 @@ class TestUsersAPI(APITestCase):
self.assertEqual(response.status_code, 200)
body = loads(response.content.decode())
self.assertEqual(body["user"]["avatar"], "bar")
def test_session_delete(self):
"""Ensure sessions are deleted when a user is deactivated"""
user = create_test_admin_user()
session_id = generate_id()
AuthenticatedSession.objects.create(
user=user,
session_key=session_id,
last_ip="",
)
cache.set(KEY_PREFIX + session_id, "foo")
self.client.force_login(self.admin)
response = self.client.patch(
reverse("authentik_api:user-detail", kwargs={"pk": user.pk}),
data={
"is_active": False,
},
)
self.assertEqual(response.status_code, 200)
self.assertIsNone(cache.get(KEY_PREFIX + session_id))
self.assertFalse(AuthenticatedSession.objects.filter(session_key=session_id).exists())

View File

@ -235,9 +235,11 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
data = CertificateGenerationSerializer(data=request.data)
if not data.is_valid():
return Response(data.errors, status=400)
raw_san = data.validated_data.get("subject_alt_name", "")
sans = raw_san.split(",") if raw_san != "" else []
builder = CertificateBuilder(data.validated_data["common_name"])
builder.build(
subject_alt_names=data.validated_data.get("subject_alt_name", "").split(","),
subject_alt_names=sans,
validity_days=int(data.validated_data["validity_days"]),
)
instance = builder.save()

View File

@ -57,7 +57,10 @@ class CertificateBuilder:
one_day = datetime.timedelta(1, 0, 0)
self.__private_key = self.generate_private_key()
self.__public_key = self.__private_key.public_key()
alt_names: list[x509.GeneralName] = [x509.DNSName(x) for x in subject_alt_names or []]
alt_names: list[x509.GeneralName] = []
for alt_name in subject_alt_names or []:
if alt_name.strip() != "":
alt_names.append(x509.DNSName(alt_name))
self.__builder = (
x509.CertificateBuilder()
.subject_name(
@ -76,12 +79,15 @@ class CertificateBuilder:
]
)
)
.add_extension(x509.SubjectAlternativeName(alt_names), critical=True)
.not_valid_before(datetime.datetime.today() - one_day)
.not_valid_after(datetime.datetime.today() + datetime.timedelta(days=validity_days))
.serial_number(int(uuid.uuid4()))
.public_key(self.__public_key)
)
if alt_names:
self.__builder = self.__builder.add_extension(
x509.SubjectAlternativeName(alt_names), critical=True
)
self.__certificate = self.__builder.sign(
private_key=self.__private_key,
algorithm=hashes.SHA256(),

View File

@ -4,6 +4,8 @@ from json import loads
from os import makedirs
from tempfile import TemporaryDirectory
from cryptography.x509.extensions import SubjectAlternativeName
from cryptography.x509.general_name import DNSName
from django.urls import reverse
from rest_framework.test import APITestCase
@ -70,11 +72,43 @@ class TestCrypto(APITestCase):
def test_builder_api(self):
"""Test Builder (via API)"""
self.client.force_login(create_test_admin_user())
name = generate_id()
self.client.post(
reverse("authentik_api:certificatekeypair-generate"),
data={"common_name": "foo", "subject_alt_name": "bar,baz", "validity_days": 3},
data={"common_name": name, "subject_alt_name": "bar,baz", "validity_days": 3},
)
self.assertTrue(CertificateKeyPair.objects.filter(name="foo").exists())
key = CertificateKeyPair.objects.filter(name=name).first()
self.assertIsNotNone(key)
ext: SubjectAlternativeName = key.certificate.extensions[0].value
self.assertIsInstance(ext, SubjectAlternativeName)
self.assertIsInstance(ext[0], DNSName)
self.assertEqual(ext[0].value, "bar")
self.assertIsInstance(ext[1], DNSName)
self.assertEqual(ext[1].value, "baz")
def test_builder_api_empty_san(self):
"""Test Builder (via API)"""
self.client.force_login(create_test_admin_user())
name = generate_id()
self.client.post(
reverse("authentik_api:certificatekeypair-generate"),
data={"common_name": name, "subject_alt_name": "", "validity_days": 3},
)
key = CertificateKeyPair.objects.filter(name=name).first()
self.assertIsNotNone(key)
self.assertEqual(len(key.certificate.extensions), 0)
def test_builder_api_empty_san_multiple(self):
"""Test Builder (via API)"""
self.client.force_login(create_test_admin_user())
name = generate_id()
self.client.post(
reverse("authentik_api:certificatekeypair-generate"),
data={"common_name": name, "subject_alt_name": ", ", "validity_days": 3},
)
key = CertificateKeyPair.objects.filter(name=name).first()
self.assertIsNotNone(key)
self.assertEqual(len(key.certificate.extensions), 0)
def test_builder_api_invalid(self):
"""Test Builder (via API) (invalid)"""

View File

@ -448,7 +448,7 @@ class NotificationTransport(SerializerModel):
# pyright: reportGeneralTypeIssues=false
return send_mail(mail.__dict__) # pylint: disable=no-value-for-parameter
except (SMTPException, ConnectionError, OSError) as exc:
raise NotificationTransportError from exc
raise NotificationTransportError(exc) from exc
@property
def serializer(self) -> "Serializer":

View File

@ -5,13 +5,20 @@ from contextlib import contextmanager
from glob import glob
from json import dumps, loads
from json.decoder import JSONDecodeError
from pathlib import Path
from sys import argv, stderr
from time import time
from typing import Any
from typing import Any, Optional
from urllib.parse import urlparse
import yaml
from django.conf import ImproperlyConfigured
from watchdog.events import (
FileModifiedEvent,
FileSystemEvent,
FileSystemEventHandler,
)
from watchdog.observers import Observer
SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob(
"/etc/authentik/config.d/*.yml", recursive=True
@ -38,9 +45,47 @@ class ConfigLoader:
A variable like AUTHENTIK_POSTGRESQL__HOST would translate to postgresql.host"""
loaded_file = []
observer: Observer
class FSObserver(FileSystemEventHandler):
"""File system observer"""
loader: "ConfigLoader"
path: str
container: Optional[dict] = None
key: Optional[str] = None
def __init__(
self,
loader: "ConfigLoader",
path: str,
container: Optional[dict] = None,
key: Optional[str] = None,
) -> None:
super().__init__()
self.loader = loader
self.path = path
self.container = container
self.key = key
def on_any_event(self, event: FileSystemEvent):
if not isinstance(event, FileModifiedEvent):
return
if event.is_directory:
return
if event.src_path != self.path:
return
if self.container and self.key:
with open(self.path, "r", encoding="utf8") as _file:
self.container[self.key] = _file.read()
else:
self.loader.log("info", "Updating from changed file", file=self.path)
self.loader.update_from_file(self.path, watch=False)
def __init__(self):
super().__init__()
self.observer = Observer()
self.observer.start()
self.__config = {}
base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "../.."))
for path in SEARCH_PATHS:
@ -81,11 +126,11 @@ class ConfigLoader:
root[key] = self.update(root.get(key, {}), value)
else:
if isinstance(value, str):
value = self.parse_uri(value)
value = self.parse_uri(value, root, key)
root[key] = value
return root
def parse_uri(self, value: str) -> str:
def parse_uri(self, value: str, container: dict[str, Any], key: Optional[str] = None, ) -> str:
"""Parse string values which start with a URI"""
url = urlparse(value)
if url.scheme == "env":
@ -93,13 +138,23 @@ class ConfigLoader:
if url.scheme == "file":
try:
with open(url.path, "r", encoding="utf8") as _file:
value = _file.read().strip()
value = _file.read()
if key:
self.observer.schedule(
ConfigLoader.FSObserver(
self,
url.path,
container,
key,
),
Path(url.path).parent,
)
except OSError as exc:
self.log("error", f"Failed to read config value from {url.path}: {exc}")
value = url.query
return value
def update_from_file(self, path: str):
def update_from_file(self, path: str, watch=True):
"""Update config from file contents"""
try:
with open(path, encoding="utf8") as file:
@ -107,6 +162,8 @@ class ConfigLoader:
self.update(self.__config, yaml.safe_load(file))
self.log("debug", "Loaded config", file=path)
self.loaded_file.append(path)
if watch:
self.observer.schedule(ConfigLoader.FSObserver(self, path), Path(path).parent)
except yaml.YAMLError as exc:
raise ImproperlyConfigured from exc
except PermissionError as exc:
@ -181,13 +238,12 @@ class ConfigLoader:
if comp not in root:
root[comp] = {}
root = root.get(comp, {})
root[path_parts[-1]] = value
self.parse_uri(value, root, path_parts[-1])
def y_bool(self, path: str, default=False) -> bool:
"""Wrapper for y that converts value into boolean"""
return str(self.y(path, default)).lower() == "true"
CONFIG = ConfigLoader()
if __name__ == "__main__":

View File

@ -5,7 +5,7 @@ from tempfile import mkstemp
from django.conf import ImproperlyConfigured
from django.test import TestCase
from authentik.lib.config import ENV_PREFIX, ConfigLoader
from authentik.lib.config import CONFIG, ENV_PREFIX, ConfigLoader
class TestConfig(TestCase):
@ -31,8 +31,8 @@ class TestConfig(TestCase):
"""Test URI parsing (environment)"""
config = ConfigLoader()
environ["foo"] = "bar"
self.assertEqual(config.parse_uri("env://foo"), "bar")
self.assertEqual(config.parse_uri("env://foo?bar"), "bar")
self.assertEqual(config.parse_uri("env://foo", {}), "bar")
self.assertEqual(config.parse_uri("env://foo?bar", {}), "bar")
def test_uri_file(self):
"""Test URI parsing (file load)"""
@ -41,8 +41,8 @@ class TestConfig(TestCase):
write(file, "foo".encode())
_, file2_name = mkstemp()
chmod(file2_name, 0o000) # Remove all permissions so we can't read the file
self.assertEqual(config.parse_uri(f"file://{file_name}"), "foo")
self.assertEqual(config.parse_uri(f"file://{file2_name}?def"), "def")
self.assertEqual(config.parse_uri(f"file://{file_name}", {}), "foo")
self.assertEqual(config.parse_uri(f"file://{file2_name}?def", {}), "def")
unlink(file_name)
unlink(file2_name)
@ -59,3 +59,13 @@ class TestConfig(TestCase):
config.update_from_file(file2_name)
unlink(file_name)
unlink(file2_name)
def test_update(self):
"""Test change to file"""
file, file_name = mkstemp()
write(file, b"test")
CONFIG.y_set("test.file", f"file://{file_name}")
self.assertEqual(CONFIG.y("test.file"), "test")
write(file, "test2")
self.assertEqual(CONFIG.y("test.file"), "test2")
unlink(file_name)

View File

@ -183,7 +183,7 @@ class DockerController(BaseController):
try:
self.client.images.pull(image)
except DockerException: # pragma: no cover
image = f"goauthentik.io/{self.outpost.type}:latest"
image = f"ghcr.io/goauthentik/{self.outpost.type}:latest"
self.client.images.pull(image)
return image

View File

@ -4,6 +4,7 @@ from django.urls.base import reverse
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.policies.password.models import PasswordPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
@ -16,6 +17,7 @@ class TestPasswordPolicyFlow(FlowTestCase):
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
password_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password",
label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD,

View File

@ -1,14 +1,42 @@
"""JWKS tests"""
import base64
import json
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import load_der_x509_certificate
from django.urls.base import reverse
from jwt import PyJWKSet
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert, create_test_flow
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.oauth2.tests.utils import OAuthTestCase
TEST_CORDS_CERT = """
-----BEGIN CERTIFICATE-----
MIIB6jCCAZCgAwIBAgIRAOsdE3N7zETzs+7shTXGj5wwCgYIKoZIzj0EAwIwHjEc
MBoGA1UEAwwTYXV0aGVudGlrIDIwMjIuMTIuMjAeFw0yMzAxMTYyMjU2MjVaFw0y
NDAxMTIyMjU2MjVaMHgxTDBKBgNVBAMMQ0NsbDR2TzFJSGxvdFFhTGwwMHpES2tM
WENYdzRPUFF2eEtZN1NrczAuc2VsZi1zaWduZWQuZ29hdXRoZW50aWsuaW8xEjAQ
BgNVBAoMCWF1dGhlbnRpazEUMBIGA1UECwwLU2VsZi1zaWduZWQwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAQAwOGam7AKOi5LKmb9lK1rAzA2JTppqrFiIaUdjqmH
ZICJP00Wt0dfqOtEjgMEv1Hhu1DmKZn2ehvpxwPSzBr5o1UwUzBRBgNVHREBAf8E
RzBFgkNCNkw4YlI0UldJRU42NUZLamdUTzV1YmRvNUZWdkpNS2lxdjFZeTRULnNl
bGYtc2lnbmVkLmdvYXV0aGVudGlrLmlvMAoGCCqGSM49BAMCA0gAMEUCIC/JAfnl
uC30ihqepbiMCaTaPMbL8Ka2Lk92IYfMhf46AiEAz9Kmv6HF2D4MK54iwhz2WqvF
8vo+OiGdTQ1Qoj7fgYU=
-----END CERTIFICATE-----
"""
TEST_CORDS_KEY = """
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKy6mPLJc5v71InMMvYaxyXI3xXpwQTPLyAYWVFnZHVioAoGCCqGSM49
AwEHoUQDQgAEAMDhmpuwCjouSypm/ZStawMwNiU6aaqxYiGlHY6ph2SAiT9NFrdH
X6jrRI4DBL9R4btQ5imZ9nob6ccD0swa+Q==
-----END EC PRIVATE KEY-----
"""
class TestJWKS(OAuthTestCase):
"""Test JWKS view"""
@ -29,6 +57,8 @@ class TestJWKS(OAuthTestCase):
body = json.loads(response.content.decode())
self.assertEqual(len(body["keys"]), 1)
PyJWKSet.from_dict(body)
key = body["keys"][0]
load_der_x509_certificate(base64.b64decode(key["x5c"][0]), default_backend()).public_key()
def test_hs256(self):
"""Test JWKS request with HS256"""
@ -60,3 +90,25 @@ class TestJWKS(OAuthTestCase):
body = json.loads(response.content.decode())
self.assertEqual(len(body["keys"]), 1)
PyJWKSet.from_dict(body)
def test_ecdsa_coords_mismatched(self):
"""Test JWKS request with ES256"""
cert = CertificateKeyPair.objects.create(
name=generate_id(),
key_data=TEST_CORDS_KEY,
certificate_data=TEST_CORDS_CERT,
)
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid",
signing_key=cert,
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})
)
body = json.loads(response.content.decode())
self.assertEqual(len(body["keys"]), 1)
PyJWKSet.from_dict(body)

View File

@ -15,27 +15,49 @@ from cryptography.hazmat.primitives.serialization import Encoding
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.views import View
from jwt.utils import base64url_encode
from authentik.core.models import Application
from authentik.crypto.models import CertificateKeyPair
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
def b64_enc(number: int) -> str:
"""Convert number to base64-encoded octet-value"""
length = ((number).bit_length() + 7) // 8
number_bytes = number.to_bytes(length, "big")
final = urlsafe_b64encode(number_bytes).rstrip(b"=")
return final.decode("ascii")
# See https://notes.salrahman.com/generate-es256-es384-es512-private-keys/
# and _CURVE_TYPES in the same file as the below curve files
ec_crv_map = {
SECP256R1: "P-256",
SECP384R1: "P-384",
SECP521R1: "P-512",
SECP521R1: "P-521",
}
min_length_map = {
SECP256R1: 32,
SECP384R1: 48,
SECP521R1: 66,
}
# https://github.com/jpadilla/pyjwt/issues/709
def bytes_from_int(val: int, min_length: int = 0) -> bytes:
"""Custom bytes_from_int that accepts a minimum length"""
remaining = val
byte_length = 0
while remaining != 0:
remaining >>= 8
byte_length += 1
length = max([byte_length, min_length])
return val.to_bytes(length, "big", signed=False)
def to_base64url_uint(val: int, min_length: int = 0) -> bytes:
"""Custom to_base64url_uint that accepts a minimum length"""
if val < 0:
raise ValueError("Must be a positive integer")
int_bytes = bytes_from_int(val, min_length)
if len(int_bytes) == 0:
int_bytes = b"\x00"
return base64url_encode(int_bytes)
class JWKSView(View):
@ -51,24 +73,25 @@ class JWKSView(View):
public_key: RSAPublicKey = private_key.public_key()
public_numbers = public_key.public_numbers()
key_data = {
"kid": key.kid,
"kty": "RSA",
"alg": JWTAlgorithms.RS256,
"use": "sig",
"kid": key.kid,
"n": b64_enc(public_numbers.n),
"e": b64_enc(public_numbers.e),
"n": to_base64url_uint(public_numbers.n).decode(),
"e": to_base64url_uint(public_numbers.e).decode(),
}
elif isinstance(private_key, EllipticCurvePrivateKey):
public_key: EllipticCurvePublicKey = private_key.public_key()
public_numbers = public_key.public_numbers()
curve_type = type(public_key.curve)
key_data = {
"kid": key.kid,
"kty": "EC",
"alg": JWTAlgorithms.ES256,
"use": "sig",
"kid": key.kid,
"x": b64_enc(public_numbers.x),
"y": b64_enc(public_numbers.y),
"crv": ec_crv_map.get(type(public_key.curve), public_key.curve.name),
"x": to_base64url_uint(public_numbers.x, min_length_map[curve_type]).decode(),
"y": to_base64url_uint(public_numbers.y, min_length_map[curve_type]).decode(),
"crv": ec_crv_map.get(curve_type, public_key.curve.name),
}
else:
return key_data

View File

@ -38,7 +38,6 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
try:
defaults = self.build_group_properties(group_dn, **attributes)
defaults["parent"] = self._source.sync_parent_group
self._logger.debug("Creating group with attributes", **defaults)
if "name" not in defaults:
raise IntegrityError("Name was not set by propertymappings")
# Special check for `users` field, as this is an M2M relation, and cannot be sync'd
@ -51,6 +50,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
},
defaults,
)
self._logger.debug("Created group with attributes", **defaults)
except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,

View File

@ -33,11 +33,10 @@ class LDAPSyncTests(TestCase):
"""Test Cached auth"""
self.source.property_mappings.set(
LDAPPropertyMapping.objects.filter(
Q(name__startswith="authentik default LDAP Mapping")
| Q(name__startswith="authentik default Active Directory Mapping")
Q(managed__startswith="goauthentik.io/sources/ldap/default-")
| Q(managed__startswith="goauthentik.io/sources/ldap/ms-")
)
)
self.source.save()
connection = PropertyMock(return_value=mock_ad_connection(LDAP_PASSWORD))
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)

View File

@ -1,5 +1,5 @@
"""Duo stage"""
from django.http import HttpRequest, HttpResponse
from django.http import HttpResponse
from django.utils.timezone import now
from rest_framework.fields import CharField
@ -10,7 +10,6 @@ from authentik.flows.challenge import (
ChallengeTypes,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import InvalidStageError
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
@ -68,13 +67,6 @@ class AuthenticatorDuoStageView(ChallengeStageView):
}
)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
self.logger.debug("No pending user, continuing")
return self.executor.stage_ok()
return super().get(request, *args, **kwargs)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
# Duo Challenge has already been validated
stage: AuthenticatorDuoStage = self.executor.current_stage

View File

@ -76,13 +76,17 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
return self.send_generic(token, device)
raise ValueError(f"invalid provider {self.provider}")
def get_message(self, token: str) -> str:
"""Get SMS message"""
return _("Use this code to authenticate in authentik: %(token)s" % {"token": token})
def send_twilio(self, token: str, device: "SMSDevice"):
"""send sms via twilio provider"""
client = Client(self.account_sid, self.auth)
try:
message = client.messages.create(
to=device.phone_number, from_=self.from_number, body=token
to=device.phone_number, from_=self.from_number, body=self.get_message(token)
)
LOGGER.debug("Sent SMS", to=device, message=message.sid)
except TwilioRestException as exc:
@ -95,6 +99,7 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
"From": self.from_number,
"To": device.phone_number,
"Body": token,
"Message": self.get_message(token),
}
if self.mapping:

View File

@ -12,9 +12,9 @@ from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
ChallengeTypes,
ErrorDetailSerializer,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_sms.models import (
AuthenticatorSMSStage,
@ -47,15 +47,9 @@ class AuthenticatorSMSChallengeResponse(ChallengeResponse):
def validate(self, attrs: dict) -> dict:
"""Check"""
stage: AuthenticatorSMSStage = self.device.stage
if "code" not in attrs:
self.device.phone_number = attrs["phone_number"]
hashed_number = hash_phone_number(self.device.phone_number)
query = Q(phone_number=hashed_number) | Q(phone_number=self.device.phone_number)
if SMSDevice.objects.filter(query, stage=self.stage.executor.current_stage.pk).exists():
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message
stage.send(self.device.token, self.device)
self.stage.validate_and_send(attrs["phone_number"])
return super().validate(attrs)
if not self.device.verify_token(str(attrs["code"])):
raise ValidationError(_("Code does not match"))
@ -68,6 +62,17 @@ class AuthenticatorSMSStageView(ChallengeStageView):
response_class = AuthenticatorSMSChallengeResponse
def validate_and_send(self, phone_number: str):
"""Validate phone number and send message"""
stage: AuthenticatorSMSStage = self.executor.current_stage
hashed_number = hash_phone_number(phone_number)
query = Q(phone_number=hashed_number) | Q(phone_number=phone_number)
if SMSDevice.objects.filter(query, stage=stage.pk).exists():
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
stage.send(device.token, device)
def _has_phone_number(self) -> Optional[str]:
context = self.executor.plan.context
if "phone" in context.get(PLAN_CONTEXT_PROMPT, {}):
@ -95,24 +100,23 @@ class AuthenticatorSMSStageView(ChallengeStageView):
return response
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
self.logger.debug("No pending user, continuing")
return self.executor.stage_ok()
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if SMSDevice.objects.filter(user=user).exists():
return self.executor.stage_ok()
user = self.get_pending_user()
stage: AuthenticatorSMSStage = self.executor.current_stage
if SESSION_KEY_SMS_DEVICE not in self.request.session:
device = SMSDevice(user=user, confirmed=False, stage=stage, name="SMS Device")
device.generate_token(commit=False)
self.request.session[SESSION_KEY_SMS_DEVICE] = device
if phone_number := self._has_phone_number():
device.phone_number = phone_number
self.request.session[SESSION_KEY_SMS_DEVICE] = device
try:
self.validate_and_send(phone_number)
except ValidationError as exc:
response = AuthenticatorSMSChallengeResponse()
response._errors.setdefault("phone_number", [])
response._errors["phone_number"].append(ErrorDetailSerializer(exc.detail))
return self.challenge_invalid(response)
return super().get(request, *args, **kwargs)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:

View File

@ -80,6 +80,39 @@ class AuthenticatorSMSStageTests(FlowTestCase):
phone_number_required=False,
)
def test_stage_context_data(self):
"""test stage context data"""
self.client.get(
reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}),
)
sms_send_mock = MagicMock()
with (
patch(
(
"authentik.stages.authenticator_sms.stage."
"AuthenticatorSMSStageView._has_phone_number"
),
MagicMock(
return_value="1234",
),
),
patch(
"authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
sms_send_mock,
),
):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
sms_send_mock.assert_called_once()
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=False,
)
def test_stage_submit_full(self):
"""test stage (submit)"""
self.client.get(

View File

@ -1,7 +1,8 @@
"""TOTP Setup stage"""
from urllib.parse import quote
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.fields import CharField, IntegerField
@ -56,7 +57,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
data={
"type": ChallengeTypes.NATIVE.value,
"config_url": device.config_url.replace(
OTP_TOTP_ISSUER, slugify(self.request.tenant.branding_title)
OTP_TOTP_ISSUER, quote(self.request.tenant.branding_title)
),
}
)

View File

@ -9,9 +9,10 @@ from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowStageBinding, NotConfiguredAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.stage import StageView
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import FlowExecutorView
from authentik.flows.views.executor import SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_validate.challenge import (
@ -20,10 +21,14 @@ from authentik.stages.authenticator_validate.challenge import (
validate_challenge_webauthn,
)
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_validate.stage import AuthenticatorValidateStageView
from authentik.stages.authenticator_validate.stage import (
SESSION_KEY_DEVICE_CHALLENGES,
AuthenticatorValidateStageView,
)
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.user_login.models import UserLoginStage
class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
@ -185,10 +190,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
def test_validate_challenge(self):
"""Test webauthn"""
request = get_request("/")
request.user = self.user
WebAuthnDevice.objects.create(
device = WebAuthnDevice.objects.create(
user=self.user,
public_key=(
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
@ -204,49 +206,134 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
)
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=flow, current_stage=stage), request=request
)
request = get_request("/")
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
session = self.client.session
plan = FlowPlan(flow_pk=flow.pk.hex)
plan.append_stage(stage)
plan.append_stage(UserLoginStage(name=generate_id()))
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session[SESSION_KEY_PLAN] = plan
session[SESSION_KEY_DEVICE_CHALLENGES] = [
{
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
}
]
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
(
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1"
"jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
)
)
request.session.save()
session.save()
stage_view = AuthenticatorValidateStageView(
FlowExecutorView(flow=flow, current_stage=stage), request=request
)
request.META["SERVER_NAME"] = "localhost"
request.META["SERVER_PORT"] = "9000"
validate_challenge_webauthn(
{
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"type": "public-key",
"assertionClientExtensions": "{}",
"response": {
"clientDataJSON": (
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4TGZo"
"ckQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLUJxYTU1"
"NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJjcm9z"
"c09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3Qg"
"Y29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczov"
"L2dvby5nbC95YWJQZXgifQ==",
),
"signature": (
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
),
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"userHandle": None,
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={
"webauthn": {
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"type": "public-key",
"assertionClientExtensions": "{}",
"response": {
"clientDataJSON": (
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4T"
"GZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLU"
"JxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDA"
"iLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUi"
"OiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuI"
"FNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ==",
),
"signature": (
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
),
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"userHandle": None,
},
},
},
stage_view,
self.user,
SERVER_NAME="localhost",
SERVER_PORT="9000",
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_validate_challenge_userless(self):
"""Test webauthn"""
device = WebAuthnDevice.objects.create(
user=self.user,
public_key=(
"pQECAyYgASFYIGsBLkklToCQkT7qJT_bJYN1sEc1oJdbnmoOc43i0J"
"H6IlggLTXytuhzFVYYAK4PQNj8_coGrbbzSfUxdiPAcZTQCyU"
),
credential_id="QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
sign_count=4,
rp_id=generate_id(),
)
flow = create_test_flow()
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
)
session = self.client.session
plan = FlowPlan(flow_pk=flow.pk.hex)
plan.append_stage(stage)
plan.append_stage(UserLoginStage(name=generate_id()))
session[SESSION_KEY_PLAN] = plan
session[SESSION_KEY_DEVICE_CHALLENGES] = [
{
"device_class": device.__class__.__name__.lower().replace("device", ""),
"device_uid": device.pk,
"challenge": {},
}
]
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
(
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1"
"jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
)
)
session.save()
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={
"webauthn": {
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"rawId": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
"type": "public-key",
"assertionClientExtensions": "{}",
"response": {
"clientDataJSON": (
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvNWx4T"
"GZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQWdPNFdoLU"
"JxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjkwMDA"
"iLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUi"
"OiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuI"
"FNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ==",
),
"signature": (
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
"AiAqtOK6mIZPk62kZN0OzFsHfuvu_RlOl7zlqSNzDdz_Ag=="
),
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABQ==",
"userHandle": None,
},
},
},
SERVER_NAME="localhost",
SERVER_PORT="9000",
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_validate_challenge_invalid(self):
"""Test webauthn"""

View File

@ -26,7 +26,6 @@ from authentik.flows.challenge import (
ChallengeTypes,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage, WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
@ -113,13 +112,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
}
)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
self.logger.debug("No pending user, continuing")
return self.executor.stage_ok()
return super().get(request, *args, **kwargs)
def get_response_instance(self, data: QueryDict) -> AuthenticatorWebAuthnChallengeResponse:
response: AuthenticatorWebAuthnChallengeResponse = super().get_response_instance(data)
response.request = self.request

View File

@ -17,7 +17,7 @@
<tr>
<td class="content-block">
{% blocktrans %}
You recently requested to change your password for you authentik account. Use the button below to set a new password.
You recently requested to change your password for your authentik account. Use the button below to set a new password.
{% endblocktrans %}
</td>
</tr>

View File

@ -62,9 +62,7 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertNotIn(
"You recently requested to change your password", mail.outbox[0].alternatives[0][0]
)
self.assertNotIn("Password Reset", mail.outbox[0].alternatives[0][0])
def test_without_user(self):
"""Test without pending user"""

View File

@ -2,6 +2,7 @@
from typing import Optional
from deepmerge import always_merger
from django.core.exceptions import ValidationError
from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext_lazy as _
@ -41,7 +42,11 @@ class InvitationStageView(StageView):
token = self.get_token()
if not token:
return None
invite: Invitation = Invitation.objects.filter(pk=token).first()
try:
invite: Invitation = Invitation.objects.filter(pk=token).first()
except ValidationError:
self.logger.debug("invalid invitation", token=token)
return None
if not invite:
self.logger.debug("invalid invitation", token=token)
return None

View File

@ -42,6 +42,7 @@ class PromptSerializer(ModelSerializer):
model = Prompt
fields = [
"pk",
"name",
"field_key",
"label",
"type",
@ -59,5 +60,5 @@ class PromptViewSet(UsedByMixin, ModelViewSet):
queryset = Prompt.objects.all().prefetch_related("promptstage_set")
serializer_class = PromptSerializer
filterset_fields = ["field_key", "label", "type", "placeholder"]
search_fields = ["field_key", "label", "type", "placeholder"]
filterset_fields = ["field_key", "name", "label", "type", "placeholder"]
search_fields = ["field_key", "name", "label", "type", "placeholder"]

View File

@ -0,0 +1,40 @@
# Generated by Django 4.1.5 on 2023-01-23 19:42
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def set_generated_name(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Prompt = apps.get_model("authentik_stages_prompt", "prompt")
for prompt in Prompt.objects.using(db_alias).all():
name = prompt.field_key
stage = prompt.promptstage_set.order_by("name").first()
if stage:
name += "_" + stage.name
prompt.name = name
prompt.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_prompt", "0008_alter_prompt_type"),
]
operations = [
migrations.AddField(
model_name="prompt",
name="name",
field=models.TextField(default="", unique=False, db_index=False, blank=False),
preserve_default=False,
),
migrations.RunPython(code=set_generated_name),
migrations.AlterField(
model_name="prompt",
name="name",
field=models.TextField(unique=True),
),
]

View File

@ -96,6 +96,7 @@ class Prompt(SerializerModel):
"""Single Prompt, part of a prompt stage."""
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(unique=True, blank=False)
field_key = models.TextField(
help_text=_("Name of the form field, also used to store the value")

View File

@ -30,6 +30,7 @@ class TestPromptStage(FlowTestCase):
self.factory = RequestFactory()
self.flow = create_test_flow()
username_prompt = Prompt.objects.create(
name=generate_id(),
field_key="username_prompt",
label="USERNAME_LABEL",
type=FieldTypes.USERNAME,
@ -37,6 +38,7 @@ class TestPromptStage(FlowTestCase):
placeholder="USERNAME_PLACEHOLDER",
)
text_prompt = Prompt.objects.create(
name=generate_id(),
field_key="text_prompt",
label="TEXT_LABEL",
type=FieldTypes.TEXT,
@ -44,6 +46,7 @@ class TestPromptStage(FlowTestCase):
placeholder="TEXT_PLACEHOLDER",
)
email_prompt = Prompt.objects.create(
name=generate_id(),
field_key="email_prompt",
label="EMAIL_LABEL",
type=FieldTypes.EMAIL,
@ -51,6 +54,7 @@ class TestPromptStage(FlowTestCase):
placeholder="EMAIL_PLACEHOLDER",
)
password_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password_prompt",
label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD,
@ -58,6 +62,7 @@ class TestPromptStage(FlowTestCase):
placeholder="PASSWORD_PLACEHOLDER",
)
password2_prompt = Prompt.objects.create(
name=generate_id(),
field_key="password2_prompt",
label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD,
@ -65,6 +70,7 @@ class TestPromptStage(FlowTestCase):
placeholder="PASSWORD_PLACEHOLDER",
)
number_prompt = Prompt.objects.create(
name=generate_id(),
field_key="number_prompt",
label="NUMBER_LABEL",
type=FieldTypes.NUMBER,
@ -72,12 +78,14 @@ class TestPromptStage(FlowTestCase):
placeholder="NUMBER_PLACEHOLDER",
)
hidden_prompt = Prompt.objects.create(
name=generate_id(),
field_key="hidden_prompt",
type=FieldTypes.HIDDEN,
required=True,
placeholder="HIDDEN_PLACEHOLDER",
)
static_prompt = Prompt.objects.create(
name=generate_id(),
field_key="static_prompt",
type=FieldTypes.STATIC,
required=True,

View File

@ -11,9 +11,9 @@ def migrate_to_user_creation_mode(apps: Apps, schema_editor: BaseDatabaseSchemaE
for stage in UserWriteStage.objects.using(schema_editor.connection.alias).all():
if stage.can_create_users:
stage.user_creation_mode = UserCreationMode.NEVER_CREATE
else:
stage.user_creation_mode = UserCreationMode.CREATE_WHEN_REQUIRED
else:
stage.user_creation_mode = UserCreationMode.NEVER_CREATE
stage.save()

View File

@ -17,9 +17,10 @@ entries:
placeholder_expression: false
required: true
type: text
identifiers:
field_key: username
label: Username
identifiers:
name: default-source-enrollment-field-username
id: prompt-field-username
model: authentik_stages_prompt.prompt
- attrs:

View File

@ -21,9 +21,10 @@ entries:
placeholder_expression: true
required: true
type: text
identifiers:
field_key: username
label: Username
identifiers:
name: default-user-settings-field-username
id: prompt-field-username
model: authentik_stages_prompt.prompt
- attrs:
@ -36,9 +37,10 @@ entries:
placeholder_expression: true
required: true
type: text
identifiers:
field_key: name
label: Name
identifiers:
name: default-user-settings-field-name
id: prompt-field-name
model: authentik_stages_prompt.prompt
- attrs:
@ -51,9 +53,10 @@ entries:
placeholder_expression: true
required: true
type: email
identifiers:
field_key: email
label: Email
identifiers:
name: default-user-settings-field-email
id: prompt-field-email
model: authentik_stages_prompt.prompt
- attrs:
@ -66,9 +69,10 @@ entries:
placeholder_expression: true
required: true
type: ak-locale
identifiers:
field_key: attributes.settings.locale
label: Locale
identifiers:
name: default-user-settings-field-locale
id: prompt-field-locale
model: authentik_stages_prompt.prompt
- attrs:

View File

@ -19,10 +19,11 @@ entries:
required: true
sub_text: ''
type: static
id: prompt-field-header
identifiers:
field_key: oobe-header-text
label: oobe-header-text
id: prompt-field-header
identifiers:
name: initial-setup-field-header
model: authentik_stages_prompt.prompt
- attrs:
order: 101
@ -31,10 +32,11 @@ entries:
required: true
sub_text: ''
type: email
id: prompt-field-email
identifiers:
field_key: email
label: Email
id: prompt-field-email
identifiers:
name: initial-setup-field-email
model: authentik_stages_prompt.prompt
- attrs:
order: 300
@ -43,10 +45,11 @@ entries:
required: true
sub_text: ''
type: password
id: prompt-field-password
identifiers:
field_key: password
label: Password
id: prompt-field-password
identifiers:
name: initial-setup-field-password
model: authentik_stages_prompt.prompt
- attrs:
order: 301
@ -55,10 +58,11 @@ entries:
required: true
sub_text: ''
type: password
id: prompt-field-password-repeat
identifiers:
field_key: password_repeat
label: Password (repeat)
id: prompt-field-password-repeat
identifiers:
name: initial-setup-field-password-repeat
model: authentik_stages_prompt.prompt
- attrs:
expression: |

View File

@ -17,9 +17,10 @@ entries:
placeholder_expression: false
required: true
type: password
identifiers:
field_key: password
label: Password
identifiers:
name: default-password-change-field-password
id: prompt-field-password
model: authentik_stages_prompt.prompt
- attrs:
@ -28,9 +29,10 @@ entries:
placeholder_expression: false
required: true
type: password
identifiers:
field_key: password_repeat
label: Password (repeat)
identifiers:
name: default-password-change-field-password-repeat
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
- attrs:

View File

@ -13,56 +13,61 @@ entries:
title: Welcome to authentik!
designation: enrollment
authentication: require_unauthenticated
- identifiers:
- id: prompt-field-username
model: authentik_stages_prompt.prompt
identifiers:
name: default-enrollment-field-username
attrs:
field_key: username
label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
attrs:
type: username
required: true
placeholder: Username
placeholder_expression: false
order: 0
- identifiers:
field_key: password
label: Password
name: default-enrollment-field-password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
type: password
required: true
placeholder: Password
placeholder_expression: false
order: 0
- identifiers:
field_key: password_repeat
label: Password (repeat)
name: default-enrollment-field-password-repeat
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
type: password
required: true
placeholder: Password (repeat)
placeholder_expression: false
order: 1
- identifiers:
field_key: name
label: Name
name: default-enrollment-field-name
id: prompt-field-name
model: authentik_stages_prompt.prompt
attrs:
field_key: name
label: Name
type: text
required: true
placeholder: Name
placeholder_expression: false
order: 0
- identifiers:
field_key: email
label: Email
name: default-enrollment-field-email
id: prompt-field-email
model: authentik_stages_prompt.prompt
attrs:
field_key: email
label: Email
type: email
required: true
placeholder: Email

View File

@ -14,55 +14,60 @@ entries:
designation: enrollment
authentication: require_unauthenticated
- identifiers:
field_key: username
label: Username
name: default-enrollment-field-username
id: prompt-field-username
model: authentik_stages_prompt.prompt
attrs:
field_key: username
label: Username
type: username
required: true
placeholder: Username
placeholder_expression: false
order: 0
- identifiers:
field_key: password
label: Password
name: default-enrollment-field-password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
type: password
required: true
placeholder: Password
placeholder_expression: false
order: 0
- identifiers:
field_key: password_repeat
label: Password (repeat)
name: default-enrollment-field-password-repeat
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
type: password
required: true
placeholder: Password (repeat)
placeholder_expression: false
order: 1
- identifiers:
field_key: name
label: Name
name: default-enrollment-field-name
id: prompt-field-name
model: authentik_stages_prompt.prompt
attrs:
field_key: name
label: Name
type: text
required: true
placeholder: Name
placeholder_expression: false
order: 0
- identifiers:
field_key: email
label: Email
name: default-enrollment-field-email
id: prompt-field-email
model: authentik_stages_prompt.prompt
attrs:
field_key: email
label: Email
type: email
required: true
placeholder: Email

View File

@ -14,22 +14,24 @@ entries:
designation: recovery
authentication: require_unauthenticated
- identifiers:
field_key: password
label: Password
name: default-recovery-field-password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
type: password
required: true
placeholder: Password
order: 0
placeholder_expression: false
- identifiers:
field_key: password_repeat
label: Password (repeat)
name: default-recovery-field-password-repeat
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
type: password
required: true
placeholder: Password (repeat)

View File

@ -0,0 +1,70 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/description: Migrate to 2023.2, remove unused prompt fields
name: Migration - Remove old prompt fields
entries:
- model: authentik_stages_prompt.prompt
identifiers:
name: admin_email_stage-default-oobe-password
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: attributes.settings.locale_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: email_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: name_default-user-settings
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: oobe-header-text_stage-default-oobe-password
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: password_default-password-change-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: password_repeat_default-password-change-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent
- model: authentik_stages_prompt.prompt
identifiers:
name: username_default-source-enrollment-prompt
attrs:
field_key: foo
label: foo
type: text
state: absent

View File

@ -32,7 +32,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.2}
restart: unless-stopped
command: server
environment:
@ -47,10 +47,10 @@ services:
env_file:
- .env
ports:
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.2}
restart: unless-stopped
command: worker
environment:

2
go.mod
View File

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

4
go.sum
View File

@ -382,8 +382,8 @@ go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZp
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
goauthentik.io/api/v3 v3.2022122.8 h1:6yJ7w1K7/h79IJC9VDUV0pb12oSQnIOcghzCCPz3sEU=
goauthentik.io/api/v3 v3.2022122.8/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
goauthentik.io/api/v3 v3.2023012.2 h1:aP/JlZCxGaPuV+vPabL3Niz7lWAYnNolrINYSFBDFG0=
goauthentik.io/api/v3 v3.2023012.2/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=

View File

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

View File

@ -1,8 +1,11 @@
package ak
import "context"
type Outpost interface {
Start() error
Stop() error
Refresh() error
TimerFlowCacheExpiry()
TimerFlowCacheExpiry(context.Context)
Type() string
}

View File

@ -1,15 +1,16 @@
package ak
import (
"context"
"time"
)
func (a *APIController) startPeriodicalTasks() {
go a.Server.TimerFlowCacheExpiry()
go func() {
for range time.Tick(time.Duration(a.GlobalConfig.CacheTimeoutFlows) * time.Second) {
a.logger.WithField("timer", "cache-timeout").Debug("Running periodical tasks")
a.Server.TimerFlowCacheExpiry()
}
}()
ctx, canc := context.WithCancel(context.Background())
defer canc()
go a.Server.TimerFlowCacheExpiry(ctx)
for range time.Tick(time.Duration(a.GlobalConfig.CacheTimeoutFlows) * time.Second) {
a.logger.WithField("timer", "cache-timeout").Debug("Running periodical tasks")
a.Server.TimerFlowCacheExpiry(ctx)
}
}

View File

@ -143,6 +143,10 @@ func (fe *FlowExecutor) GetSession() *http.Cookie {
return fe.session
}
func (fe *FlowExecutor) SetSession(s *http.Cookie) {
fe.session = s
}
// WarmUp Ensure authentik's flow cache is warmed up
func (fe *FlowExecutor) WarmUp() error {
gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")

View File

@ -1,9 +1,14 @@
package bind
import "github.com/nmcclain/ldap"
import (
"context"
"github.com/nmcclain/ldap"
)
type Binder interface {
GetUsername(string) (string, error)
Bind(username string, req *Request) (ldap.LDAPResultCode, error)
TimerFlowCacheExpiry()
Unbind(username string, req *Request) (ldap.LDAPResultCode, error)
TimerFlowCacheExpiry(context.Context)
}

View File

@ -0,0 +1,98 @@
package direct
import (
"context"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/ldap/metrics"
)
func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
fe := flow.NewFlowExecutor(req.Context(), db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW
passed, err := fe.Execute()
flags := flags.UserFlags{
Session: fe.GetSession(),
}
db.si.SetFlags(req.BindDN, &flags)
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "flow_error",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultInvalidCredentials, nil
}
if !passed {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "invalid_credentials",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().Info("Invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil
}
access, err := fe.CheckApplicationAccess(db.si.GetAppSlug())
if !access {
req.Log().Info("Access denied for user")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_denied",
"app": db.si.GetAppSlug(),
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil
}
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_check_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil
}
req.Log().Info("User has access")
uisp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "user_info_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
cs := db.SearchAccessCheck(userInfo.User)
flags.UserPk = userInfo.User.Pk
flags.CanSearch = cs != nil
db.si.SetFlags(req.BindDN, &flags)
if flags.CanSearch {
req.Log().WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
return ldap.LDAPResultSuccess, nil
}

View File

@ -5,16 +5,10 @@ import (
"errors"
"strings"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/outpost/ldap/server"
"goauthentik.io/internal/outpost/ldap/utils"
)
@ -53,90 +47,6 @@ func (db *DirectBinder) GetUsername(dn string) (string, error) {
return "", errors.New("failed to find cn")
}
func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
fe := flow.NewFlowExecutor(req.Context(), db.si.GetFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW
passed, err := fe.Execute()
flags := flags.UserFlags{
Session: fe.GetSession(),
}
db.si.SetFlags(req.BindDN, flags)
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "flow_error",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultInvalidCredentials, nil
}
if !passed {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "invalid_credentials",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().Info("Invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil
}
access, err := fe.CheckApplicationAccess(db.si.GetAppSlug())
if !access {
req.Log().Info("Access denied for user")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_denied",
"app": db.si.GetAppSlug(),
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil
}
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_check_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil
}
req.Log().Info("User has access")
uisp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.bind.user_info")
// Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "user_info_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
cs := db.SearchAccessCheck(userInfo.User)
flags.UserPk = userInfo.User.Pk
flags.CanSearch = cs != nil
db.si.SetFlags(req.BindDN, flags)
if flags.CanSearch {
req.Log().WithField("group", cs).Info("Allowed access to search")
}
uisp.Finish()
return ldap.LDAPResultSuccess, nil
}
// SearchAccessCheck Check if the current user is allowed to search
func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string {
for _, group := range user.Groups {
@ -153,8 +63,8 @@ func (db *DirectBinder) SearchAccessCheck(user api.UserSelf) *string {
return nil
}
func (db *DirectBinder) TimerFlowCacheExpiry() {
fe := flow.NewFlowExecutor(context.Background(), db.si.GetFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{})
func (db *DirectBinder) TimerFlowCacheExpiry(ctx context.Context) {
fe := flow.NewFlowExecutor(ctx, db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{})
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Params.Add("goauthentik.io/outpost/ldap-warmup", "true")

View File

@ -0,0 +1,29 @@
package direct
import (
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind"
)
func (db *DirectBinder) Unbind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
flags := db.si.GetFlags(req.BindDN)
if flags == nil || flags.Session == nil {
return ldap.LDAPResultSuccess, nil
}
fe := flow.NewFlowExecutor(req.Context(), db.si.GetInvalidationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"boundDN": req.BindDN,
"client": req.RemoteAddr(),
"requestId": req.ID(),
})
fe.SetSession(flags.Session)
fe.DelegateClientIP(req.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
_, err := fe.Execute()
if err != nil {
db.log.WithError(err).Warning("failed to logout user")
}
db.si.SetFlags(req.BindDN, nil)
return ldap.LDAPResultSuccess, nil
}

View File

@ -27,10 +27,11 @@ type ProviderInstance struct {
searcher search.Searcher
binder bind.Binder
appSlug string
flowSlug string
s *LDAPServer
log *log.Entry
appSlug string
authenticationFlowSlug string
invalidationFlowSlug string
s *LDAPServer
log *log.Entry
tlsServerName *string
cert *tls.Certificate
@ -79,9 +80,13 @@ func (pi *ProviderInstance) GetFlags(dn string) *flags.UserFlags {
return flags
}
func (pi *ProviderInstance) SetFlags(dn string, flag flags.UserFlags) {
func (pi *ProviderInstance) SetFlags(dn string, flag *flags.UserFlags) {
pi.boundUsersMutex.Lock()
pi.boundUsers[dn] = &flag
if flag == nil {
delete(pi.boundUsers, dn)
} else {
pi.boundUsers[dn] = flag
}
pi.boundUsersMutex.Unlock()
}
@ -89,8 +94,12 @@ func (pi *ProviderInstance) GetAppSlug() string {
return pi.appSlug
}
func (pi *ProviderInstance) GetFlowSlug() string {
return pi.flowSlug
func (pi *ProviderInstance) GetAuthenticationFlowSlug() string {
return pi.authenticationFlowSlug
}
func (pi *ProviderInstance) GetInvalidationFlowSlug() string {
return pi.invalidationFlowSlug
}
func (pi *ProviderInstance) GetSearchAllowedGroups() []*strfmt.UUID {

View File

@ -1,6 +1,7 @@
package ldap
import (
"context"
"crypto/tls"
"net"
"sync"
@ -40,6 +41,7 @@ func NewServer(ac *ak.APIController) *LDAPServer {
}
ls.defaultCert = &defaultCert
s.BindFunc("", ls)
s.UnbindFunc("", ls)
s.SearchFunc("", ls)
return ls
}
@ -92,9 +94,13 @@ func (ls *LDAPServer) Start() error {
return nil
}
func (ls *LDAPServer) TimerFlowCacheExpiry() {
func (ls *LDAPServer) Stop() error {
return nil
}
func (ls *LDAPServer) TimerFlowCacheExpiry(ctx context.Context) {
for _, p := range ls.providers {
ls.log.WithField("flow", p.flowSlug).Debug("Pre-heating flow cache")
p.binder.TimerFlowCacheExpiry()
ls.log.WithField("flow", p.authenticationFlowSlug).Debug("Pre-heating flow cache")
p.binder.TimerFlowCacheExpiry(ctx)
}
}

View File

@ -28,6 +28,16 @@ func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
return nil
}
func (ls *LDAPServer) getInvalidationFlow() string {
req, _, err := ls.ac.Client.CoreApi.CoreTenantsCurrentRetrieve(context.Background()).Execute()
if err != nil {
ls.log.WithError(err).Warning("failed to fetch tenant config")
return ""
}
flow := req.GetFlowInvalidation()
return flow
}
func (ls *LDAPServer) Refresh() error {
outposts, _, err := ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()).Execute()
if err != nil {
@ -37,6 +47,7 @@ func (ls *LDAPServer) Refresh() error {
return errors.New("no ldap provider defined")
}
providers := make([]*ProviderInstance, len(outposts.Results))
invalidationFlow := ls.getInvalidationFlow()
for idx, provider := range outposts.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUUsers, *provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=%s,%s", constants.OUGroups, *provider.BaseDn))
@ -53,22 +64,23 @@ func (ls *LDAPServer) Refresh() error {
}
providers[idx] = &ProviderInstance{
BaseDN: *provider.BaseDn,
VirtualGroupDN: virtualGroupDN,
GroupDN: groupDN,
UserDN: userDN,
appSlug: provider.ApplicationSlug,
flowSlug: provider.BindFlowSlug,
searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())},
boundUsersMutex: sync.RWMutex{},
boundUsers: users,
s: ls,
log: logger,
tlsServerName: provider.TlsServerName,
uidStartNumber: *provider.UidStartNumber,
gidStartNumber: *provider.GidStartNumber,
outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk,
BaseDN: *provider.BaseDn,
VirtualGroupDN: virtualGroupDN,
GroupDN: groupDN,
UserDN: userDN,
appSlug: provider.ApplicationSlug,
authenticationFlowSlug: provider.BindFlowSlug,
invalidationFlowSlug: invalidationFlow,
searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())},
boundUsersMutex: sync.RWMutex{},
boundUsers: users,
s: ls,
log: logger,
tlsServerName: provider.TlsServerName,
uidStartNumber: *provider.UidStartNumber,
gidStartNumber: *provider.GidStartNumber,
outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk,
}
if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp)

View File

@ -11,7 +11,8 @@ type LDAPServerInstance interface {
GetAPIClient() *api.APIClient
GetOutpostName() string
GetFlowSlug() string
GetAuthenticationFlowSlug() string
GetInvalidationFlowSlug() string
GetAppSlug() string
GetSearchAllowedGroups() []*strfmt.UUID
@ -32,7 +33,7 @@ type LDAPServerInstance interface {
UsersForGroup(api.Group) []string
GetFlags(dn string) *flags.UserFlags
SetFlags(dn string, flags flags.UserFlags)
SetFlags(dn string, flags *flags.UserFlags)
GetBaseEntry() *ldap.Entry
GetNeededObjects(int, string, string) (bool, bool)

View File

@ -0,0 +1,53 @@
package ldap
import (
"net"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/metrics"
)
func (ls *LDAPServer) Unbind(boundDN string, conn net.Conn) (ldap.LDAPResultCode, error) {
req, span := bind.NewRequest(boundDN, "", conn)
selectedApp := ""
defer func() {
span.Finish()
metrics.Requests.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Unbind request")
}()
defer func() {
err := recover()
if err == nil {
return
}
log.WithError(err.(error)).Error("recover in bind request")
sentry.CaptureException(err.(error))
}()
for _, instance := range ls.providers {
username, err := instance.binder.GetUsername(boundDN)
if err == nil {
selectedApp = instance.GetAppSlug()
return instance.binder.Unbind(username, req)
} else {
req.Log().WithError(err).Debug("Username not for instance")
}
}
req.Log().WithField("request", "unbind").Warning("No provider found for request")
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"reason": "no_provider",
"app": "",
}).Inc()
return ldap.LDAPResultOperationsError, nil
}

View File

@ -95,7 +95,12 @@ func IncludeObjectClass(searchOC string, ocs map[string]bool) bool {
return true
}
return ocs[searchOC]
for key, value := range ocs {
if strings.EqualFold(key, searchOC) {
return value
}
}
return false
}
func GetContainerEntry(filterOC string, dn string, ou string) *ldap.Entry {

View File

@ -70,19 +70,28 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
ks = oidc.NewRemoteKeySet(ctx, p.OidcConfiguration.JwksUri)
}
var verifier = oidc.NewVerifier(p.OidcConfiguration.Issuer, ks, &oidc.Config{
ClientID: *p.ClientId,
SupportedSigningAlgs: []string{"RS256", "HS256"},
})
redirectUri, _ := url.Parse(p.ExternalHost)
redirectUri.Path = path.Join(redirectUri.Path, "/outpost.goauthentik.io/callback")
redirectUri.RawQuery = url.Values{
CallbackSignature: []string{"true"},
}.Encode()
managed := false
if m := ak.Outpost.Managed.Get(); m != nil {
managed = *m == "goauthentik.io/outposts/embedded"
}
// Configure an OpenID Connect aware OAuth2 client.
endpoint := GetOIDCEndpoint(p, ak.Outpost.Config["authentik_host"].(string))
endpoint := GetOIDCEndpoint(
p,
ak.Outpost.Config["authentik_host"].(string),
managed,
)
verifier := oidc.NewVerifier(endpoint.Issuer, ks, &oidc.Config{
ClientID: *p.ClientId,
SupportedSigningAlgs: []string{"RS256", "HS256"},
})
oauth2Config := oauth2.Config{
ClientID: *p.ClientId,
ClientSecret: *p.ClientSecret,

View File

@ -15,11 +15,23 @@ type OIDCEndpoint struct {
TokenIntrospection string
EndSessionEndpoint string
JwksUri string
Issuer string
}
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoint {
func updateURL(rawUrl string, scheme string, host string) string {
u, err := url.Parse(rawUrl)
if err != nil {
return rawUrl
}
u.Host = host
u.Scheme = scheme
return u.String()
}
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint {
authUrl := p.OidcConfiguration.AuthorizationEndpoint
endUrl := p.OidcConfiguration.EndSessionEndpoint
tokenUrl := p.OidcConfiguration.TokenEndpoint
jwksUrl := p.OidcConfiguration.JwksUri
if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found && browserHost != "" {
host := os.Getenv("AUTHENTIK_HOST")
@ -30,26 +42,15 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoin
ep := OIDCEndpoint{
Endpoint: oauth2.Endpoint{
AuthURL: authUrl,
TokenURL: p.OidcConfiguration.TokenEndpoint,
TokenURL: tokenUrl,
AuthStyle: oauth2.AuthStyleInParams,
},
EndSessionEndpoint: endUrl,
JwksUri: jwksUrl,
TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint,
Issuer: p.OidcConfiguration.Issuer,
}
authU, err := url.Parse(authUrl)
if err != nil {
return ep
}
endU, err := url.Parse(endUrl)
if err != nil {
return ep
}
jwksU, err := url.Parse(jwksUrl)
if err != nil {
return ep
}
if authU.Host != "localhost:8000" {
if !embedded {
return ep
}
if authentikHost == "" {
@ -60,14 +61,10 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoin
if err != nil {
return ep
}
authU.Host = aku.Host
authU.Scheme = aku.Scheme
endU.Host = aku.Host
endU.Scheme = aku.Scheme
jwksU.Host = aku.Host
jwksU.Scheme = aku.Scheme
ep.AuthURL = authU.String()
ep.EndSessionEndpoint = endU.String()
ep.JwksUri = jwksU.String()
ep.AuthURL = updateURL(authUrl, aku.Scheme, aku.Host)
ep.EndSessionEndpoint = updateURL(endUrl, aku.Scheme, aku.Host)
ep.JwksUri = updateURL(jwksUrl, aku.Scheme, aku.Host)
ep.TokenURL = updateURL(tokenUrl, aku.Scheme, aku.Host)
ep.Issuer = updateURL(ep.Issuer, aku.Scheme, aku.Host)
return ep
}

View File

@ -81,7 +81,7 @@ func (ps *ProxyServer) Type() string {
return "proxy"
}
func (ps *ProxyServer) TimerFlowCacheExpiry() {}
func (ps *ProxyServer) TimerFlowCacheExpiry(context.Context) {}
func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate {
app, ok := ps.apps[serverName]
@ -163,6 +163,10 @@ func (ps *ProxyServer) Start() error {
return nil
}
func (ps *ProxyServer) Stop() error {
return nil
}
func (ps *ProxyServer) serve(listener net.Listener) {
srv := &http.Server{Handler: ps.mux}

View File

@ -61,7 +61,7 @@ func (ws *WebServer) configureStatic() {
func (ws *WebServer) staticHeaderMiddleware(h http.Handler) http.Handler {
etagHandler := etag.Handler(h, false)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "\"public, no-transform\"")
w.Header().Set("Cache-Control", "public, no-transform")
w.Header().Set("X-authentik-version", constants.VERSION)
w.Header().Set("Vary", "X-authentik-version, Etag")
etagHandler.ServeHTTP(w, r)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-13 14:37+0000\n"
"POT-Creation-Date: 2023-01-23 09:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/api/tasks.py:126
#: authentik/admin/api/tasks.py:124
#, python-format
msgid "Successfully re-scheduled Task %(name)s!"
msgstr ""
@ -177,26 +177,26 @@ msgstr ""
msgid "Authenticated Sessions"
msgstr ""
#: authentik/core/sources/flow_manager.py:198
#: authentik/core/sources/flow_manager.py:197
msgid "source"
msgstr ""
#: authentik/core/sources/flow_manager.py:250
#: authentik/core/sources/flow_manager.py:248
msgid "Configured flow does not exist."
msgstr ""
#: authentik/core/sources/flow_manager.py:281
#: authentik/core/sources/flow_manager.py:333
#: authentik/core/sources/flow_manager.py:278
#: authentik/core/sources/flow_manager.py:330
#, python-format
msgid "Successfully authenticated with %(source)s!"
msgstr ""
#: authentik/core/sources/flow_manager.py:305
#: authentik/core/sources/flow_manager.py:302
#, python-format
msgid "Successfully linked %(source)s!"
msgstr ""
#: authentik/core/sources/flow_manager.py:324
#: authentik/core/sources/flow_manager.py:321
msgid "Source is not configured for enrollment."
msgstr ""
@ -255,9 +255,9 @@ msgid "Powered by authentik"
msgstr ""
#: authentik/core/views/apps.py:48
#: authentik/providers/oauth2/views/authorize.py:358
#: authentik/providers/oauth2/views/authorize.py:357
#: authentik/providers/oauth2/views/device_init.py:68
#: authentik/providers/saml/views/sso.py:69
#: authentik/providers/saml/views/sso.py:68
#, python-format
msgid "You're about to sign into %(application)s."
msgstr ""
@ -284,89 +284,89 @@ msgstr ""
msgid "Certificate-Key Pairs"
msgstr ""
#: authentik/events/models.py:288
#: authentik/events/models.py:287
msgid "Event"
msgstr ""
#: authentik/events/models.py:289
#: authentik/events/models.py:288
msgid "Events"
msgstr ""
#: authentik/events/models.py:295
#: authentik/events/models.py:294
msgid "authentik inbuilt notifications"
msgstr ""
#: authentik/events/models.py:296
#: authentik/events/models.py:295
msgid "Generic Webhook"
msgstr ""
#: authentik/events/models.py:297
#: authentik/events/models.py:296
msgid "Slack Webhook (Slack/Discord)"
msgstr ""
#: authentik/events/models.py:298
#: authentik/events/models.py:297
msgid "Email"
msgstr ""
#: authentik/events/models.py:316
#: authentik/events/models.py:315
msgid ""
"Only send notification once, for example when sending a webhook into a chat "
"channel."
msgstr ""
#: authentik/events/models.py:376
#: authentik/events/models.py:375
msgid "Severity"
msgstr ""
#: authentik/events/models.py:381
#: authentik/events/models.py:380
msgid "Dispatched for user"
msgstr ""
#: authentik/events/models.py:465
#: authentik/events/models.py:464
msgid "Notification Transport"
msgstr ""
#: authentik/events/models.py:466
#: authentik/events/models.py:465
msgid "Notification Transports"
msgstr ""
#: authentik/events/models.py:472
#: authentik/events/models.py:471
msgid "Notice"
msgstr ""
#: authentik/events/models.py:473
#: authentik/events/models.py:472
msgid "Warning"
msgstr ""
#: authentik/events/models.py:474
#: authentik/events/models.py:473
msgid "Alert"
msgstr ""
#: authentik/events/models.py:500
#: authentik/events/models.py:499
msgid "Notification"
msgstr ""
#: authentik/events/models.py:501
#: authentik/events/models.py:500
msgid "Notifications"
msgstr ""
#: authentik/events/models.py:521
#: authentik/events/models.py:520
msgid "Controls which severity level the created notifications will have."
msgstr ""
#: authentik/events/models.py:547
#: authentik/events/models.py:546
msgid "Notification Rule"
msgstr ""
#: authentik/events/models.py:548
#: authentik/events/models.py:547
msgid "Notification Rules"
msgstr ""
#: authentik/events/models.py:569
#: authentik/events/models.py:568
msgid "Webhook Mapping"
msgstr ""
#: authentik/events/models.py:570
#: authentik/events/models.py:569
msgid "Webhook Mappings"
msgstr ""
@ -374,7 +374,7 @@ msgstr ""
msgid "Task has not been run yet."
msgstr ""
#: authentik/flows/api/flows.py:297
#: authentik/flows/api/flows.py:292
#, python-format
msgid "Flow not applicable to current user/request: %(messages)s"
msgstr ""
@ -490,12 +490,12 @@ msgstr ""
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."
msgstr ""
#: authentik/outposts/api/service_connections.py:132
#: authentik/outposts/api/service_connections.py:131
msgid ""
"You can only use an empty kubeconfig when connecting to a local cluster."
msgstr ""
#: authentik/outposts/api/service_connections.py:140
#: authentik/outposts/api/service_connections.py:139
msgid "Invalid kubeconfig"
msgstr ""
@ -925,7 +925,7 @@ msgstr ""
msgid "Device Tokens"
msgstr ""
#: authentik/providers/oauth2/views/authorize.py:412
#: authentik/providers/oauth2/views/authorize.py:411
#: authentik/providers/saml/views/flows.py:88
#, python-format
msgid "Redirecting to %(app)s..."
@ -974,36 +974,42 @@ msgid ""
"with internal_host."
msgstr ""
#: authentik/providers/proxy/models.py:79
#: authentik/providers/proxy/models.py:80
msgid ""
"When enabled, this provider will intercept the authorization header and "
"authenticate requests based on its value."
msgstr ""
#: authentik/providers/proxy/models.py:86
msgid "Set HTTP-Basic Authentication"
msgstr ""
#: authentik/providers/proxy/models.py:81
#: authentik/providers/proxy/models.py:88
msgid ""
"Set a custom HTTP-Basic Authentication header based on values from authentik."
msgstr ""
#: authentik/providers/proxy/models.py:86
#: authentik/providers/proxy/models.py:93
msgid "HTTP-Basic Username Key"
msgstr ""
#: authentik/providers/proxy/models.py:96
#: authentik/providers/proxy/models.py:103
msgid "HTTP-Basic Password Key"
msgstr ""
#: authentik/providers/proxy/models.py:152
#: authentik/providers/proxy/models.py:159
msgid "Proxy Provider"
msgstr ""
#: authentik/providers/proxy/models.py:153
#: authentik/providers/proxy/models.py:160
msgid "Proxy Providers"
msgstr ""
#: authentik/providers/saml/api/providers.py:261
#: authentik/providers/saml/api/providers.py:260
msgid "Invalid XML Syntax"
msgstr ""
#: authentik/providers/saml/api/providers.py:271
#: authentik/providers/saml/api/providers.py:270
#, python-format
msgid "Failed to import Metadata: %(message)s"
msgstr ""
@ -1174,7 +1180,7 @@ msgstr ""
msgid "LDAP Property Mappings"
msgstr ""
#: authentik/sources/ldap/signals.py:58
#: authentik/sources/ldap/signals.py:56
msgid "Password does not match Active Directory Complexity."
msgstr ""
@ -1323,7 +1329,7 @@ msgstr ""
msgid "User OAuth Source Connections"
msgstr ""
#: authentik/sources/oauth/views/callback.py:103
#: authentik/sources/oauth/views/callback.py:100
#, python-format
msgid "Authentication failed: %(reason)s"
msgstr ""
@ -1460,30 +1466,35 @@ msgstr ""
msgid "Optionally modify the payload being sent to custom providers."
msgstr ""
#: authentik/stages/authenticator_sms/models.py:176
#: authentik/stages/authenticator_sms/models.py:81
#, python-format
msgid "Use this code to authenticate in authentik: %(token)s"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:181
msgid "SMS Authenticator Setup Stage"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:177
#: authentik/stages/authenticator_sms/models.py:182
msgid "SMS Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:222
#: authentik/stages/authenticator_sms/models.py:227
msgid "SMS Device"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:223
#: authentik/stages/authenticator_sms/models.py:228
msgid "SMS Devices"
msgstr ""
#: authentik/stages/authenticator_sms/stage.py:56
msgid "Invalid phone number"
#: authentik/stages/authenticator_sms/stage.py:55
#: authentik/stages/authenticator_totp/stage.py:42
#: authentik/stages/authenticator_totp/stage.py:45
msgid "Code does not match"
msgstr ""
#: authentik/stages/authenticator_sms/stage.py:61
#: authentik/stages/authenticator_totp/stage.py:41
#: authentik/stages/authenticator_totp/stage.py:44
msgid "Code does not match"
#: authentik/stages/authenticator_sms/stage.py:71
msgid "Invalid phone number"
msgstr ""
#: authentik/stages/authenticator_static/models.py:47
@ -1694,7 +1705,7 @@ msgstr ""
#: authentik/stages/email/templates/email/password_reset.html:19
msgid ""
"\n"
" You recently requested to change your password for you "
" You recently requested to change your password for your "
"authentik account. Use the button below to set a new password.\n"
" "
msgstr ""
@ -1794,7 +1805,7 @@ msgstr ""
msgid "Invitations"
msgstr ""
#: authentik/stages/invitation/stage.py:61
#: authentik/stages/invitation/stage.py:66
msgid "Invalid invite/invite not found"
msgstr ""

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

140
poetry.lock generated
View File

@ -789,63 +789,63 @@ files = [
[[package]]
name = "coverage"
version = "7.0.5"
version = "7.1.0"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"},
{file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"},
{file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"},
{file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"},
{file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"},
{file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"},
{file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"},
{file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"},
{file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"},
{file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"},
{file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"},
{file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"},
{file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"},
{file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"},
{file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"},
{file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"},
{file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"},
{file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"},
{file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"},
{file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"},
{file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"},
{file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"},
{file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"},
{file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"},
{file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"},
{file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"},
{file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"},
{file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"},
{file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"},
{file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"},
{file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"},
{file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"},
{file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"},
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"},
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"},
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"},
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"},
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"},
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"},
{file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"},
{file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"},
{file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"},
{file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"},
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"},
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"},
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"},
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"},
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"},
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"},
{file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"},
{file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"},
{file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"},
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"},
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"},
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"},
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"},
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"},
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"},
{file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"},
{file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"},
{file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"},
{file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"},
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"},
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"},
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"},
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"},
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"},
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"},
{file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"},
{file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"},
{file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"},
{file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"},
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"},
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"},
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"},
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"},
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"},
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"},
{file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"},
{file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"},
{file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"},
{file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"},
]
[package.extras]
@ -2084,27 +2084,25 @@ files = [
[[package]]
name = "paramiko"
version = "2.12.0"
version = "3.0.0"
description = "SSH2 protocol library"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.6"
files = [
{file = "paramiko-2.12.0-py2.py3-none-any.whl", hash = "sha256:b2df1a6325f6996ef55a8789d0462f5b502ea83b3c990cbb5bbe57345c6812c4"},
{file = "paramiko-2.12.0.tar.gz", hash = "sha256:376885c05c5d6aa6e1f4608aac2a6b5b0548b1add40274477324605903d9cd49"},
{file = "paramiko-3.0.0-py3-none-any.whl", hash = "sha256:6bef55b882c9d130f8015b9a26f4bd93f710e90fe7478b9dcc810304e79b3cd8"},
{file = "paramiko-3.0.0.tar.gz", hash = "sha256:fedc9b1dd43bc1d45f67f1ceca10bc336605427a46dcdf8dec6bfea3edf57965"},
]
[package.dependencies]
bcrypt = ">=3.1.3"
cryptography = ">=2.5"
pynacl = ">=1.0.1"
six = "*"
bcrypt = ">=3.2"
cryptography = ">=3.3"
pynacl = ">=1.5"
[package.extras]
all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"]
ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"]
all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
invoke = ["invoke (>=1.3)"]
invoke = ["invoke (>=2.0)"]
[[package]]
name = "pathspec"
@ -2853,14 +2851,14 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "selenium"
version = "4.7.2"
version = "4.8.0"
description = ""
category = "dev"
optional = false
python-versions = "~=3.7"
python-versions = ">=3.7"
files = [
{file = "selenium-4.7.2-py3-none-any.whl", hash = "sha256:06a1c7d9f313130b21c3218ddd8852070d0e7419afdd31f96160cd576555a5ce"},
{file = "selenium-4.7.2.tar.gz", hash = "sha256:3aefa14a28a42e520550c1cd0f29cf1d566328186ea63aa9a3e01fb265b5894d"},
{file = "selenium-4.8.0-py3-none-any.whl", hash = "sha256:20f28ee4ea9b273b4112a7df5276ebb3052f79ff6eff42a564db6143e5926683"},
{file = "selenium-4.8.0.tar.gz", hash = "sha256:fee36724d6cf0b18c73781bb8ec7be4a35ab1e2564e64e64e64da75e50e052af"},
]
[package.dependencies]
@ -2871,14 +2869,14 @@ urllib3 = {version = ">=1.26,<2.0", extras = ["socks"]}
[[package]]
name = "sentry-sdk"
version = "1.13.0"
version = "1.14.0"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "sentry-sdk-1.13.0.tar.gz", hash = "sha256:72da0766c3069a3941eadbdfa0996f83f5a33e55902a19ba399557cfee1dddcc"},
{file = "sentry_sdk-1.13.0-py2.py3-none-any.whl", hash = "sha256:b7ff6318183e551145b5c4766eb65b59ad5b63ff234dffddc5fb50340cad6729"},
{file = "sentry-sdk-1.14.0.tar.gz", hash = "sha256:273fe05adf052b40fd19f6d4b9a5556316807246bd817e5e3482930730726bb0"},
{file = "sentry_sdk-1.14.0-py2.py3-none-any.whl", hash = "sha256:72c00322217d813cf493fe76590b23a757e063ff62fec59299f4af7201dd4448"},
]
[package.dependencies]
@ -2896,7 +2894,7 @@ falcon = ["falcon (>=1.4)"]
fastapi = ["fastapi (>=0.79.0)"]
flask = ["blinker (>=1.1)", "flask (>=0.11)"]
httpx = ["httpx (>=0.16.0)"]
opentelemetry = ["opentelemetry-distro (>=0.350b0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]

View File

@ -107,10 +107,14 @@ DJANGO_SETTINGS_MODULE = "authentik.root.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"]
junit_family = "xunit2"
addopts = "-p no:celery --junitxml=unittest.xml"
filterwarnings = [
"ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning",
"ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",
]
[tool.poetry]
name = "authentik"
version = "2023.1.0"
version = "2023.1.2"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2023.1.0
version: 2023.1.2
description: Making authentication simple.
contact:
email: hello@goauthentik.io
@ -23008,6 +23008,10 @@ paths:
name: label
schema:
type: string
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
@ -34263,6 +34267,9 @@ components:
type: object
description: Prompt Serializer
properties:
name:
type: string
minLength: 1
field_key:
type: string
minLength: 1
@ -35278,6 +35285,8 @@ components:
format: uuid
readOnly: true
title: Prompt uuid
name:
type: string
field_key:
type: string
description: Name of the form field, also used to store the value
@ -35304,6 +35313,7 @@ components:
required:
- field_key
- label
- name
- pk
- type
PromptChallenge:
@ -35345,6 +35355,9 @@ components:
type: object
description: Prompt Serializer
properties:
name:
type: string
minLength: 1
field_key:
type: string
minLength: 1
@ -35373,6 +35386,7 @@ components:
required:
- field_key
- label
- name
- type
PromptStage:
type: object

View File

@ -26,8 +26,8 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_totp_validate(self):
"""test flow with otp stages"""
@ -52,10 +52,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint("default/20-flow-default-authenticator-totp-setup.yaml")
@apply_blueprint("default/flow-default-authenticator-totp-setup.yaml")
def test_totp_setup(self):
"""test TOTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
@ -98,10 +98,10 @@ class TestFlowsAuthenticator(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint("default/20-flow-default-authenticator-static-setup.yaml")
@apply_blueprint("default/flow-default-authenticator-static-setup.yaml")
def test_static_setup(self):
"""test Static OTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow")

View File

@ -41,19 +41,28 @@ class TestFlowsEnroll(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_enroll_2_step(self):
"""Test 2-step enroll flow"""
# First stage fields
username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
)
password = Prompt.objects.create(
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD
name=generate_id(),
field_key="password",
label="Password",
order=1,
type=FieldTypes.PASSWORD,
)
password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat",
label="Password (repeat)",
order=2,
@ -62,10 +71,10 @@ class TestFlowsEnroll(SeleniumTestCase):
# Second stage fields
name_field = Prompt.objects.create(
field_key="name", label="Name", order=0, type=FieldTypes.TEXT
name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
)
email = Prompt.objects.create(
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
)
# Stages
@ -107,19 +116,28 @@ class TestFlowsEnroll(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_enroll_email(self):
"""Test enroll with Email verification"""
# First stage fields
username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
name=generate_id(),
field_key="username",
label="Username",
order=0,
type=FieldTypes.TEXT,
)
password = Prompt.objects.create(
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD
name=generate_id(),
field_key="password",
label="Password",
order=1,
type=FieldTypes.PASSWORD,
)
password_repeat = Prompt.objects.create(
name=generate_id(),
field_key="password_repeat",
label="Password (repeat)",
order=2,
@ -128,10 +146,10 @@ class TestFlowsEnroll(SeleniumTestCase):
# Second stage fields
name_field = Prompt.objects.create(
field_key="name", label="Name", order=0, type=FieldTypes.TEXT
name=generate_id(), field_key="name", label="Name", order=0, type=FieldTypes.TEXT
)
email = Prompt.objects.create(
field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
name=generate_id(), field_key="email", label="E-Mail", order=1, type=FieldTypes.EMAIL
)
# Stages

View File

@ -12,8 +12,8 @@ class TestFlowsLogin(SeleniumTestCase):
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_login(self):
"""test default login flow"""

View File

@ -18,10 +18,10 @@ class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows"""
@retry()
@apply_blueprint("default/0-flow-password-change.yaml")
@apply_blueprint("default/flow-password-change.yaml")
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_password_change(self):
"""test password change flow"""

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