Compare commits

...

98 Commits

Author SHA1 Message Date
4d791f4fef release: 2022.12.3 2023-03-02 20:18:21 +01:00
c03a069f02 security: fix CVE-2023-26481 (#4832)
fix CVE-2023-26481

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-03-02 20:16:01 +01:00
040adb8ce7 website: always show build version in version dropdown
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

#3940

# Conflicts:
#	website/docusaurus.config.js
2023-02-16 14:40:07 +01:00
ac07833688 release: 2022.12.2 2023-01-05 10:01:30 +01:00
be473470a4 web/admin: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 22:40:18 +01:00
445cd5b2c4 web: fix radio label code in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 22:40:15 +01:00
805a4b766a web/admin: migrate webauthn forms to radio
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 22:40:12 +01:00
730139e43c *: improve general tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 22:40:09 +01:00
24e8915e0a providers/proxy: add tests for proxy basic auth (#4357)
* add tests for proxy basic auth

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

* stop bandit from complaining

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

* add API tests

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

* more tests

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 22:40:06 +01:00
1e01e9813d providers/saml: add prefix to entity descriptor (#4355)
add prefix to entity descriptor

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 16:44:52 +01:00
119a268eb7 web: add custom scrollbar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 11:53:01 +01:00
e887a315be providers/oauth2: correctly advertise supported response_modes_supported
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 10:21:34 +01:00
c4bb51469b website/docs: prepare 2022.12.2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-04 10:15:15 +01:00
6edc043775 web: bump eslint-plugin-custom-elements from 0.0.6 to 0.0.7 in /web (#4349)
Bumps [eslint-plugin-custom-elements](https://github.com/github/eslint-plugin-custom-elements) from 0.0.6 to 0.0.7.
- [Release notes](https://github.com/github/eslint-plugin-custom-elements/releases)
- [Commits](https://github.com/github/eslint-plugin-custom-elements/compare/v0.0.6...v0.0.7)

---
updated-dependencies:
- dependency-name: eslint-plugin-custom-elements
  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-04 09:54:58 +01:00
4379f5bc8e core: bump coverage from 7.0.2 to 7.0.3 (#4350)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.0.2 to 7.0.3.
- [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.2...7.0.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  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-04 09:54:49 +01:00
1ad56f4a13 web: bump @squoosh/cli from 0.7.2 to 0.7.3 in /web (#4348)
Bumps [@squoosh/cli](https://github.com/GoogleChromeLabs/squoosh) from 0.7.2 to 0.7.3.
- [Release notes](https://github.com/GoogleChromeLabs/squoosh/releases)
- [Commits](https://github.com/GoogleChromeLabs/squoosh/commits)

---
updated-dependencies:
- dependency-name: "@squoosh/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-04 09:54:17 +01:00
f54e82781a web/elements: fix dropdown menu closing before selecting item sometimes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-03 10:11:23 +01:00
e334d8ab00 web: bump @typescript-eslint/eslint-plugin from 5.47.1 to 5.48.0 in /web (#4342)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.47.1 to 5.48.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.48.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-03 09:56:52 +01:00
e1c0f74152 web: bump @typescript-eslint/parser from 5.47.1 to 5.48.0 in /web (#4341)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.47.1 to 5.48.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.48.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-03 09:55:21 +01:00
e8f850285e core: bump coverage from 7.0.1 to 7.0.2 (#4343)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.0.1...7.0.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  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-03 09:54:32 +01:00
4b93f40c5e providers/oauth2: fix null amr value not being removed from id_token
closes #4339

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-03 00:41:18 +01:00
57400925a4 providers/saml: don't error if no request in API serializer context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-03 00:14:16 +01:00
ffed653cae web/admin: migrate api calls to async (#4335)
migrate api calls to async

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-02 16:13:07 +01:00
ba5cd6e719 web/admin: add Radio control, search-select fixes (#4333)
* move search select to forms folder

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

* add radio, migrate smaller lists

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

* move dropdown when scrolling, hide when container out of frame

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

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-02 14:51:44 +01:00
9564894eda web/elements: trigger search select data update on connected callback
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-02 10:26:52 +01:00
042cd0b2cb core: bump django from 4.1.4 to 4.1.5 (#4332)
Bumps [django](https://github.com/django/django) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.1.4...4.1.5)

---
updated-dependencies:
- dependency-name: django
  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-02 09:52:54 +01:00
049a97a800 web: bump yaml from 2.2.0 to 2.2.1 in /web (#4325)
Bumps [yaml](https://github.com/eemeli/yaml) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: yaml
  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-02 09:29:28 +01:00
aa6668f8cb web: bump @types/codemirror from 5.60.5 to 5.60.6 in /web (#4327)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 5.60.5 to 5.60.6.
- [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-02 09:29:21 +01:00
13a129bb01 web: bump eslint from 8.30.0 to 8.31.0 in /web (#4326)
Bumps [eslint](https://github.com/eslint/eslint) from 8.30.0 to 8.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.30.0...v8.31.0)

---
updated-dependencies:
- dependency-name: eslint
  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-02 09:29:11 +01:00
0974f58367 core: bump github.com/jellydator/ttlcache/v3 from 3.0.0 to 3.0.1 (#4328)
Bumps [github.com/jellydator/ttlcache/v3](https://github.com/jellydator/ttlcache) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/jellydator/ttlcache/releases)
- [Changelog](https://github.com/jellydator/ttlcache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jellydator/ttlcache/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: github.com/jellydator/ttlcache/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-02 09:29:04 +01:00
1d59bfd16e core: bump goauthentik.io/api/v3 from 3.2022120.3 to 3.2022121.4 (#4329)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022120.3 to 3.2022121.4.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022120.3...v3.2022121.4)

---
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-02 09:28:55 +01:00
ebd73ec34f core: bump watchdog from 2.2.0 to 2.2.1 (#4330)
Bumps [watchdog](https://github.com/gorakhargosh/watchdog) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/gorakhargosh/watchdog/releases)
- [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst)
- [Commits](https://github.com/gorakhargosh/watchdog/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: watchdog
  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-02 09:28:46 +01:00
0629dee23b core: bump importlib-metadata from 5.2.0 to 6.0.0 (#4331)
Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 5.2.0 to 6.0.0.
- [Release notes](https://github.com/python/importlib_metadata/releases)
- [Changelog](https://github.com/python/importlib_metadata/blob/main/CHANGES.rst)
- [Commits](https://github.com/python/importlib_metadata/compare/v5.2.0...v6.0.0)

---
updated-dependencies:
- dependency-name: importlib-metadata
  dependency-type: direct:development
  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-02 09:28:36 +01:00
2dc0792d9e stages/email: remove unused import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-02 09:28:26 +01:00
fde848ee51 admin: remove unused import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-02 00:12:14 +01:00
e9d52282b7 admin: use matching environment for system API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:58:12 +01:00
c810628fe3 stages/email: use pending user correctly
closes #4318

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:50:57 +01:00
de0a5191f7 core: remove unused import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:50:42 +01:00
f6794829e4 web: bump API Client version (#4324)
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-01 23:45:59 +01:00
475853fb14 web: update tsconfig strictness
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:44:15 +01:00
1c1319927e web: ensure locales are built for tsc check
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:36:14 +01:00
964fdf171b web: add check compile test to prevent compile errors/warnings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:32:05 +01:00
93e20bce2e core: don't use inline_serializer for user operations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:16:44 +01:00
960a2aab74 crypto: fix type for has_key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:14:19 +01:00
2cae6596eb core: cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:01:08 +01:00
11b1eb4173 stages/email: make template tests less flaky
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 23:00:32 +01:00
ee615c2d22 web: bump API Client version (#4323)
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-01 22:57:12 +01:00
aef9a22331 web/admin: fix error in outpost form dropdown
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 22:27:23 +01:00
3980eea7c6 web/flows: rework error display, always use ak-stage-flow-error instead of shell
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 21:43:44 +01:00
d6d72489a7 web/flows: add close button to flow inspector
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 21:26:21 +01:00
9fdfb8c99b stages/dummy: add toggle to throw error for debugging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 21:25:53 +01:00
b9bb27008e web/elements: fix selection of blank elements in search-select, fix issue when re-opening dropdown
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 20:10:16 +01:00
82184b2882 web/flows: fix alternate captchas not loading
closes #4321

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 18:49:41 +01:00
f90a52c7d6 web: bump API Client version (#4322)
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-01 18:24:54 +01:00
9ea0441559 web/elements: correctly display selected empty option when blankable is enabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 18:19:32 +01:00
5cab280759 stages/captcha: fix captcha not loading correctly, add tests
closes #4320

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2023-01-01 18:15:41 +01:00
a03a64b35c web/admin: fix error when creating SAML Provider from metadata
closes #4315

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-31 12:54:42 +01:00
780b986be8 web/elements: only find pages for directly related slots
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-30 22:35:57 +01:00
9d422918b3 stages/prompt: use stage.get_pending_user() to fallback to the correct user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-30 20:38:15 +01:00
b548ccca6e web: bump API Client version (#4312)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-12-30 14:33:07 +01:00
2c42c87689 release: 2022.12.1 2022-12-30 13:43:42 +01:00
8262a47455 core: bump packaging from 21.3 to 22.0 (#4181)
* core: bump packaging from 21.3 to 22.0

Bumps [packaging](https://github.com/pypa/packaging) from 21.3 to 22.0.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/21.3...22.0)

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

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

* remove LegacyVersion

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

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-30 12:07:25 +01:00
bd56922a2f blueprints: watch blueprints directory and trigger tasks (#4309)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-30 11:30:18 +01:00
5b68942b23 core: bump goauthentik.io/api/v3 from 3.2022120.2 to 3.2022120.3 (#4310) 2022-12-30 10:08:30 +01:00
c8bd0fbb1c website/docs: prepare 2022.12.1 release
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 23:59:05 +01:00
c99798b1f2 website/docs: update release notes, remove duplicate files
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 23:28:15 +01:00
316c6966b7 web/admin: post-migration cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 23:24:43 +01:00
6a44695c48 web/admin: use flow slug as main name for flow dropdown
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 22:53:15 +01:00
c46b2d5573 web/admin: finish migration to search-select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 22:48:28 +01:00
68b58fb73c blueprints: fix error when entry with state absent doesn't exist
closes #4305

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 21:55:17 +01:00
97513467ad blueprints: disallow flow token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 21:54:56 +01:00
35678c18c5 web/admin: replace more selects with search select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 21:49:36 +01:00
5fba08c911 web/admin: replace more selects with search select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 13:15:09 +01:00
1149a61986 web/admin: replace certificate selection with ak-search-select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:56:54 +01:00
7a10872854 web/admin: replace flow selections with ak-search-select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:50:18 +01:00
4d1bcd2e19 web: bump API Client version (#4304)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-12-29 11:25:30 +01:00
8a1b6693a7 web/elements: make ak-search-select limited in height and scroll
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:14:13 +01:00
90c89aec76 web/admin: replace stage selections with ak-search-select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:14:13 +01:00
b429e24392 web/admin: replace group selections with ak-search-select
closes #4157

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:14:12 +01:00
3073b7d7e3 root: regen API schema
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 11:01:05 +01:00
e02b99bfbc web/admin: replace user selections with ak-search-select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#4157
2022-12-29 11:00:57 +01:00
6d86067cea web/elements: add grouping and descriptions to search select
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-29 10:56:12 +01:00
ce5d1fd80d blueprints: Resolve yamltags in state and model attributes (#4299)
* Fixed state and model attributes not resolving yaml tags

* Linting
2022-12-29 10:05:32 +01:00
a6755bea71 web: bump json5 from 2.2.1 to 2.2.2 in /web (#4302)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: json5
  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>
2022-12-29 09:39:59 +01:00
4cce99b207 website: bump json5 from 2.2.1 to 2.2.2 in /website (#4303)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: json5
  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>
2022-12-29 09:39:53 +01:00
d1287aa7c9 core: bump goauthentik.io/api/v3 from 3.2022114.2 to 3.2022120.2 (#4301) 2022-12-29 09:04:15 +01:00
bcbd6f7243 web: bump pyright from 1.1.285 to 1.1.286 in /web (#4300) 2022-12-29 09:03:58 +01:00
39424839c5 outposts/ldap: only use common cert if cert is configured, correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 22:50:50 +01:00
2d03bd5c89 outposts/ldap: only use common cert if cert is configured
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 21:29:36 +01:00
4d527a0ac5 web/user: fix user settings stuck loading
closes #4297

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 20:48:02 +01:00
b1020fde64 web/elements: render ak-seach-select dropdown correctly in modals
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 20:38:57 +01:00
ff13b4bb46 outposts/ldap: use configured certificate for LDAPS when all providers' certificates are identical
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 19:15:29 +01:00
f0e121c064 api: add filter backend for secret key to allow access to tenants and certificates
closes #4182

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 18:59:25 +01:00
89a3f7d004 web: bump API Client version (#4296)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-12-28 16:19:18 +01:00
e6aa4c9327 web/admin: rework outpost health
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 16:16:18 +01:00
2b2323fae7 outposts: include hostname in outpost heartbeat
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 16:07:52 +01:00
a148e611f3 web: bump API Client version (#4295)
Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-12-28 15:30:25 +01:00
b56fd5e745 website/developer-docs: list native dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-12-28 15:25:16 +01:00
174 changed files with 4616 additions and 41770 deletions

View File

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

View File

@ -27,6 +27,22 @@ jobs:
- name: Eslint
working-directory: web/
run: npm run lint
lint-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.5.1
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-client-ts
- name: TSC
working-directory: web/
run: npm run tsc
lint-prettier:
runs-on: ubuntu-latest
steps:
@ -69,6 +85,7 @@ jobs:
- lint-eslint
- lint-prettier
- lint-lit-analyse
- lint-build
runs-on: ubuntu-latest
steps:
- run: echo mark

View File

@ -126,7 +126,7 @@ gen: gen-build gen-clean gen-client-ts
web-build: web-install
cd web && npm run build
web: web-lint-fix web-lint
web: web-lint-fix web-lint web-check-compile
web-install:
cd web && npm ci
@ -144,6 +144,9 @@ web-lint:
cd web && npm run lint
cd web && npm run lit-analyse
web-check-compile:
cd web && npm run tsc
web-extract:
cd web && npm run extract

View File

@ -2,16 +2,14 @@
from os import environ
from typing import Optional
__version__ = "2022.12.0"
__version__ = "2022.12.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
def get_build_hash(fallback: Optional[str] = None) -> str:
"""Get build hash"""
build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "")
if build_hash == "" and fallback:
return fallback
return build_hash
return fallback if build_hash == "" and fallback else build_hash
def get_full_version() -> str:

View File

@ -8,7 +8,6 @@ from typing import TypedDict
from django.utils.timezone import now
from drf_spectacular.utils import extend_schema
from gunicorn import version_info as gunicorn_version
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -16,6 +15,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.utils.reflection import get_env
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
@ -69,7 +69,7 @@ class SystemSerializer(PassiveSerializer):
return {
"python_version": python_version,
"gunicorn_version": ".".join(str(x) for x in gunicorn_version),
"environment": "kubernetes" if SERVICE_HOST_ENV_NAME in os.environ else "compose",
"environment": get_env(),
"architecture": platform.machine(),
"platform": platform.platform(),
"uname": " ".join(platform.uname()),

View File

@ -1,9 +1,15 @@
"""API Authorization"""
from django.conf import settings
from django.db.models import Model
from django.db.models.query import QuerySet
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.authentication import get_authorization_header
from rest_framework.filters import BaseFilterBackend
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.api.authentication import validate_auth
class OwnerFilter(BaseFilterBackend):
@ -17,6 +23,20 @@ class OwnerFilter(BaseFilterBackend):
return queryset.filter(**{self.owner_key: request.user})
class SecretKeyFilter(DjangoFilterBackend):
"""Allow access to all objects when authenticated with secret key as token.
Replaces both DjangoFilterBackend and ObjectPermissionsFilter"""
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
auth_header = get_authorization_header(request)
token = validate_auth(auth_header)
if token and token == settings.SECRET_KEY:
return queryset
queryset = ObjectPermissionsFilter().filter_queryset(request, queryset, view)
return super().filter_queryset(request, queryset, view)
class OwnerPermissions(BasePermission):
"""Authorize requests by an object's owner matching the requesting user"""

View File

@ -68,7 +68,7 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_GEO_IP)
if CONFIG.y_bool("impersonation"):
caps.append(Capabilities.CAN_IMPERSONATE)
if settings.DEBUG:
if settings.DEBUG: # pragma: no cover
caps.append(Capabilities.CAN_DEBUG)
return caps

View File

@ -5,3 +5,9 @@ entries:
slug: "%(id)s"
model: authentik_flows.flow
state: absent
- identifiers:
name: "%(id)s"
expression: |
return True
model: authentik_policies_expression.expressionpolicy
state: absent

View File

@ -4,7 +4,8 @@ context:
policy_property: name
policy_property_value: foo-bar-baz-qux
entries:
- model: authentik_sources_oauth.oauthsource
- model: !Format ["%s", authentik_sources_oauth.oauthsource]
state: !Format ["%s", present]
identifiers:
slug: test
attrs:

View File

@ -16,7 +16,7 @@ def serializer_tester_factory(test_model: Type[SerializerModel]) -> Callable:
"""Test serializer"""
def tester(self: TestModels):
if test_model._meta.abstract:
if test_model._meta.abstract: # pragma: no cover
return
model_class = test_model()
self.assertTrue(isinstance(model_class, SerializerModel))

View File

@ -26,8 +26,8 @@ class TestBlueprintOCI(TransactionTestCase):
self.assertEqual(
BlueprintInstance(
path="https://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve_oci(),
path="oci://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve(),
"foo",
)
@ -40,7 +40,7 @@ class TestBlueprintOCI(TransactionTestCase):
with self.assertRaises(BlueprintRetrievalFailed):
BlueprintInstance(
path="https://ghcr.io/goauthentik/blueprints/test:latest"
path="oci://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve_oci()
def test_manifests_error_response(self):
@ -53,7 +53,7 @@ class TestBlueprintOCI(TransactionTestCase):
with self.assertRaises(BlueprintRetrievalFailed):
BlueprintInstance(
path="https://ghcr.io/goauthentik/blueprints/test:latest"
path="oci://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve_oci()
def test_no_matching_blob(self):
@ -72,7 +72,7 @@ class TestBlueprintOCI(TransactionTestCase):
)
with self.assertRaises(BlueprintRetrievalFailed):
BlueprintInstance(
path="https://ghcr.io/goauthentik/blueprints/test:latest"
path="oci://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve_oci()
def test_blob_error(self):
@ -93,5 +93,5 @@ class TestBlueprintOCI(TransactionTestCase):
with self.assertRaises(BlueprintRetrievalFailed):
BlueprintInstance(
path="https://ghcr.io/goauthentik/blueprints/test:latest"
path="oci://ghcr.io/goauthentik/blueprints/test:latest"
).retrieve_oci()

View File

@ -5,7 +5,7 @@ from enum import Enum
from functools import reduce
from operator import ixor
from os import getenv
from typing import Any, Literal, Optional
from typing import Any, Literal, Optional, Union
from uuid import UUID
from django.apps import apps
@ -56,8 +56,10 @@ class BlueprintEntryDesiredState(Enum):
class BlueprintEntry:
"""Single entry of a blueprint"""
model: str
state: BlueprintEntryDesiredState = field(default=BlueprintEntryDesiredState.PRESENT)
model: Union[str, "YAMLTag"]
state: Union[BlueprintEntryDesiredState, "YAMLTag"] = field(
default=BlueprintEntryDesiredState.PRESENT
)
conditions: list[Any] = field(default_factory=list)
identifiers: dict[str, Any] = field(default_factory=dict)
attrs: Optional[dict[str, Any]] = field(default_factory=dict)
@ -103,6 +105,14 @@ class BlueprintEntry:
"""Get attributes of this entry, with all yaml tags resolved"""
return self.tag_resolver(self.identifiers, blueprint)
def get_state(self, blueprint: "Blueprint") -> BlueprintEntryDesiredState:
"""Get the blueprint state, with yaml tags resolved if present"""
return BlueprintEntryDesiredState(self.tag_resolver(self.state, blueprint))
def get_model(self, blueprint: "Blueprint") -> str:
"""Get the blueprint model, with yaml tags resolved if present"""
return str(self.tag_resolver(self.model, blueprint))
def check_all_conditions_match(self, blueprint: "Blueprint") -> bool:
"""Check all conditions of this entry match (evaluate to True)"""
return all(self.tag_resolver(self.conditions, blueprint))

View File

@ -34,7 +34,7 @@ from authentik.core.models import (
Source,
UserSourceConnection,
)
from authentik.flows.models import Stage
from authentik.flows.models import FlowToken, Stage
from authentik.lib.models import SerializerModel
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
@ -60,6 +60,8 @@ def is_model_allowed(model: type[Model]) -> bool:
PolicyBindingModel,
# Classes that have other dependencies
AuthenticatedSession,
# Classes which are only internally managed
FlowToken,
)
return model not in excluded_models and issubclass(model, (SerializerModel, BaseMetaModel))
@ -148,7 +150,7 @@ class Importer:
self.logger.debug("One or more conditions of this entry are not fulfilled, skipping")
return None
model_app_label, model_name = entry.model.split(".")
model_app_label, model_name = entry.get_model(self.__import).split(".")
model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
# Don't use isinstance since we don't want to check for inheritance
if not is_model_allowed(model):
@ -184,7 +186,7 @@ class Importer:
serializer_kwargs = {}
model_instance = existing_models.first()
if not isinstance(model(), BaseMetaModel) and model_instance:
if entry.state == BlueprintEntryDesiredState.CREATED:
if entry.get_state(self.__import) == BlueprintEntryDesiredState.CREATED:
self.logger.debug("instance exists, skipping")
return None
self.logger.debug(
@ -237,7 +239,7 @@ class Importer:
"""Apply (create/update) models yaml"""
self.__pk_map = {}
for entry in self.__import.entries:
model_app_label, model_name = entry.model.split(".")
model_app_label, model_name = entry.get_model(self.__import).split(".")
try:
model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
except LookupError:
@ -254,7 +256,8 @@ class Importer:
if not serializer:
continue
if entry.state in [
state = entry.get_state(self.__import)
if state in [
BlueprintEntryDesiredState.PRESENT,
BlueprintEntryDesiredState.CREATED,
]:
@ -263,9 +266,9 @@ class Importer:
self.__pk_map[entry.identifiers["pk"]] = model.pk
entry._state = BlueprintEntryState(model)
self.logger.debug("updated model", model=model)
elif entry.state == BlueprintEntryDesiredState.ABSENT:
elif state == BlueprintEntryDesiredState.ABSENT:
instance: Optional[Model] = serializer.instance
if instance:
if instance.pk:
instance.delete()
self.logger.debug("deleted model", mode=instance)
continue

View File

@ -10,6 +10,13 @@ from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger
from watchdog.events import (
FileCreatedEvent,
FileModifiedEvent,
FileSystemEvent,
FileSystemEventHandler,
)
from watchdog.observers import Observer
from yaml import load
from yaml.error import YAMLError
@ -32,6 +39,7 @@ from authentik.lib.config import CONFIG
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
_file_watcher_started = False
@dataclass
@ -45,6 +53,39 @@ class BlueprintFile:
meta: Optional[BlueprintMetadata] = field(default=None)
def start_blueprint_watcher():
"""Start blueprint watcher, if it's not running already."""
# This function might be called twice since it's called on celery startup
# pylint: disable=global-statement
global _file_watcher_started
if _file_watcher_started:
return
observer = Observer()
observer.schedule(BlueprintEventHandler(), CONFIG.y("blueprints_dir"), recursive=True)
observer.start()
_file_watcher_started = True
class BlueprintEventHandler(FileSystemEventHandler):
"""Event handler for blueprint events"""
def on_any_event(self, event: FileSystemEvent):
if not isinstance(event, (FileCreatedEvent, FileModifiedEvent)):
return
if event.is_directory:
return
if isinstance(event, FileCreatedEvent):
LOGGER.debug("new blueprint file created, starting discovery")
blueprints_discover.delay()
if isinstance(event, FileModifiedEvent):
path = Path(event.src_path)
root = Path(CONFIG.y("blueprints_dir")).absolute()
rel_path = str(path.relative_to(root))
for instance in BlueprintInstance.objects.filter(path=rel_path):
LOGGER.debug("modified blueprint file, starting apply", instance=instance)
apply_blueprint.delay(instance.pk.hex)
@CELERY_APP.task(
throws=(DatabaseError, ProgrammingError, InternalError),
)
@ -60,8 +101,7 @@ def blueprints_find():
"""Find blueprints and return valid ones"""
blueprints = []
root = Path(CONFIG.y("blueprints_dir"))
for file in root.glob("**/*.yaml"):
path = Path(file)
for path in root.glob("**/*.yaml"):
LOGGER.debug("found blueprint", path=str(path))
with open(path, "r", encoding="utf-8") as blueprint_file:
try:

View File

@ -5,7 +5,7 @@ from django.db.models.query import QuerySet
from django.http import Http404
from django_filters.filters import CharFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, JSONField
@ -17,7 +17,7 @@ from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict
from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.core.models import Group, User
@ -120,6 +120,12 @@ class GroupFilter(FilterSet):
fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
class UserAccountSerializer(PassiveSerializer):
"""Account adding/removing operations"""
pk = IntegerField(required=True)
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""
@ -144,12 +150,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
@permission_required(None, ["authentik_core.add_user"])
@extend_schema(
request=inline_serializer(
"UserAccountSerializer",
{
"pk": IntegerField(required=True),
},
),
request=UserAccountSerializer,
responses={
204: OpenApiResponse(description="User added"),
404: OpenApiResponse(description="User not found"),
@ -174,12 +175,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
@permission_required(None, ["authentik_core.add_user"])
@extend_schema(
request=inline_serializer(
"UserAccountSerializer",
{
"pk": IntegerField(required=True),
},
),
request=UserAccountSerializer,
responses={
204: OpenApiResponse(description="User added"),
404: OpenApiResponse(description="User not found"),

View File

@ -1,5 +1,4 @@
"""Property Mapping Evaluator"""
from traceback import format_tb
from typing import Optional
from django.db.models import Model
@ -8,6 +7,7 @@ from django.http import HttpRequest
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.types import PolicyRequest
@ -38,7 +38,7 @@ class PropertyMappingEvaluator(BaseEvaluator):
def handle_error(self, exc: Exception, expression_source: str):
"""Exception Handler"""
error_string = "\n".join(format_tb(exc.__traceback__) + [str(exc)])
error_string = exception_to_string(exc)
event = Event.new(
EventAction.PROPERTY_MAPPING_EXCEPTION,
expression=expression_source,

View File

@ -13,6 +13,7 @@
<link rel="stylesheet" type="text/css" href="{% static 'dist/page.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/empty-state.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/spinner.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/dropdown.css' %}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">

View File

@ -35,7 +35,7 @@ def source_tester_factory(test_model: type[Stage]) -> Callable:
def tester(self: TestModels):
model_class = None
if test_model._meta.abstract:
if test_model._meta.abstract: # pragma: no cover
model_class = test_model.__bases__[0]()
else:
model_class = test_model()

View File

@ -15,12 +15,14 @@ from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_sche
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DateTimeField, IntegerField, SerializerMethodField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authorization import SecretKeyFilter
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
@ -203,9 +205,17 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
filterset_class = CertificateKeyPairFilter
ordering = ["name"]
search_fields = ["name"]
filter_backends = [SecretKeyFilter, OrderingFilter, SearchFilter]
@extend_schema(
parameters=[
# Override the type for `has_key` above
OpenApiParameter(
"has_key",
bool,
required=False,
description="Only return certificate-key pairs with keys",
),
OpenApiParameter("include_details", bool, default=True),
]
)

View File

@ -39,6 +39,11 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
request.session[SESSION_LOGIN_EVENT] = event
def get_login_event(request: HttpRequest) -> Optional[Event]:
"""Wrapper to get login event that can be mocked in tests"""
return request.session.get(SESSION_LOGIN_EVENT, None)
@receiver(user_logged_out)
# pylint: disable=unused-argument
def on_user_logged_out(sender, request: HttpRequest, user: User, **_):

View File

@ -1,7 +1,6 @@
"""Challenge helpers"""
from dataclasses import asdict, is_dataclass
from enum import Enum
from traceback import format_tb
from typing import TYPE_CHECKING, Optional, TypedDict
from uuid import UUID
@ -9,8 +8,10 @@ from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import JsonResponse
from rest_framework.fields import CharField, ChoiceField, DictField
from rest_framework.request import Request
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.utils.errors import exception_to_string
if TYPE_CHECKING:
from authentik.flows.stage import StageView
@ -90,32 +91,31 @@ class WithUserInfoChallenge(Challenge):
pending_user_avatar = CharField()
class FlowErrorChallenge(WithUserInfoChallenge):
class FlowErrorChallenge(Challenge):
"""Challenge class when an unhandled error occurs during a stage. Normal users
are shown an error message, superusers are shown a full stacktrace."""
component = CharField(default="xak-flow-error")
type = CharField(default=ChallengeTypes.NATIVE.value)
component = CharField(default="ak-stage-flow-error")
request_id = CharField()
error = CharField(required=False)
traceback = CharField(required=False)
def __init__(self, *args, **kwargs):
request = kwargs.pop("request", None)
error = kwargs.pop("error", None)
super().__init__(*args, **kwargs)
def __init__(self, request: Optional[Request] = None, error: Optional[Exception] = None):
super().__init__(data={})
if not request or not error:
return
self.request_id = request.request_id
self.initial_data["request_id"] = request.request_id
from authentik.core.models import USER_ATTRIBUTE_DEBUG
if request.user and request.user.is_authenticated:
if request.user.is_superuser or request.user.group_attributes(request).get(
USER_ATTRIBUTE_DEBUG, False
):
self.error = error
self.traceback = "".join(format_tb(self.error.__traceback__))
self.initial_data["error"] = str(error)
self.initial_data["traceback"] = exception_to_string(error)
class AccessDeniedChallenge(WithUserInfoChallenge):

View File

@ -162,7 +162,7 @@ class FlowExecutorView(APIView):
token.delete()
if not isinstance(plan, FlowPlan):
return None
plan.context[PLAN_CONTEXT_IS_RESTORED] = True
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
self._logger.debug("f(exec): restored flow plan from token", plan=plan)
return plan
@ -255,7 +255,7 @@ class FlowExecutorView(APIView):
message=exception_to_string(exc),
).from_http(self.request)
challenge = FlowErrorChallenge(self.request, exc)
challenge.is_valid()
challenge.is_valid(raise_exception=True)
return to_stage_response(self.request, HttpChallengeResponse(challenge))
@extend_schema(

View File

@ -19,7 +19,7 @@ def model_tester_factory(test_model: type[Stage]) -> Callable:
def tester(self: TestModels):
try:
model_class = None
if test_model._meta.abstract:
if test_model._meta.abstract: # pragma: no cover
return
model_class = test_model()
self.assertTrue(issubclass(model_class.serializer, BaseSerializer))

View File

@ -48,14 +48,14 @@ def get_apps():
def get_env() -> str:
"""Get environment in which authentik is currently running"""
if SERVICE_HOST_ENV_NAME in os.environ:
return "kubernetes"
if "CI" in os.environ:
return "ci"
if Path("/tmp/authentik-mode").exists(): # nosec
return "compose"
if CONFIG.y_bool("debug"):
return "dev"
if SERVICE_HOST_ENV_NAME in os.environ:
return "kubernetes"
if Path("/tmp/authentik-mode").exists(): # nosec
return "compose"
if "AK_APPLIANCE" in os.environ:
return os.environ["AK_APPLIANCE"]
return "custom"

View File

@ -19,7 +19,13 @@ from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.core.models import Provider
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType, default_outpost_config
from authentik.outposts.models import (
Outpost,
OutpostConfig,
OutpostState,
OutpostType,
default_outpost_config,
)
from authentik.providers.ldap.models import LDAPProvider
from authentik.providers.proxy.models import ProxyProvider
@ -96,6 +102,7 @@ class OutpostDefaultConfigSerializer(PassiveSerializer):
class OutpostHealthSerializer(PassiveSerializer):
"""Outpost health status"""
uid = CharField(read_only=True)
last_seen = DateTimeField(read_only=True)
version = CharField(read_only=True)
version_should = CharField(read_only=True)
@ -105,6 +112,8 @@ class OutpostHealthSerializer(PassiveSerializer):
build_hash = CharField(read_only=True, required=False)
build_hash_should = CharField(read_only=True, required=False)
hostname = CharField(read_only=True, required=False)
class OutpostFilter(FilterSet):
"""Filter for Outposts"""
@ -145,13 +154,16 @@ class OutpostViewSet(UsedByMixin, ModelViewSet):
outpost: Outpost = self.get_object()
states = []
for state in outpost.state:
state: OutpostState
states.append(
{
"uid": state.uid,
"last_seen": state.last_seen,
"version": state.version,
"version_should": state.version_should,
"version_outdated": state.version_outdated,
"build_hash": state.build_hash,
"hostname": state.hostname,
"build_hash_should": get_build_hash(),
}
)

View File

@ -98,6 +98,7 @@ class OutpostConsumer(AuthJsonConsumer):
if self.channel_name not in state.channel_ids:
state.channel_ids.append(self.channel_name)
state.last_seen = datetime.now()
state.hostname = msg.args.get("hostname", "")
if not self.first_msg:
GAUGE_OUTPOSTS_CONNECTED.labels(

View File

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from model_utils.managers import InheritanceManager
from packaging.version import LegacyVersion, Version, parse
from packaging.version import Version, parse
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@ -429,8 +429,9 @@ class OutpostState:
channel_ids: list[str] = field(default_factory=list)
last_seen: Optional[datetime] = field(default=None)
version: Optional[str] = field(default=None)
version_should: Version | LegacyVersion = field(default=OUR_VERSION)
version_should: Version = field(default=OUR_VERSION)
build_hash: str = field(default="")
hostname: str = field(default="")
_outpost: Optional[Outpost] = field(default=None)

View File

@ -22,8 +22,7 @@ from rest_framework.serializers import Serializer
from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event
from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.events.signals import get_login_event
from authentik.lib.generators import generate_code_fixed_length, generate_id, generate_key
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
@ -419,6 +418,8 @@ class IDToken:
id_dict.pop("nonce")
if not self.c_hash:
id_dict.pop("c_hash")
if not self.amr:
id_dict.pop("amr")
id_dict.pop("claims")
id_dict.update(self.claims)
return id_dict
@ -503,8 +504,8 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
# Fallback in case we can't find any login events
auth_time = now
if SESSION_LOGIN_EVENT in request.session:
auth_event: Event = request.session[SESSION_LOGIN_EVENT]
auth_event = get_login_event(request)
if auth_event:
auth_time = auth_event.created
# Also check which method was used for authentication
method = auth_event.context.get(PLAN_CONTEXT_METHOD, "")
@ -526,6 +527,7 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
exp=exp_time,
iat=iat_time,
auth_time=auth_timestamp,
amr=amr if amr else None,
)
# Include (or not) user standard claims in the id_token.

View File

@ -1,10 +1,13 @@
"""Test authorize view"""
from unittest.mock import MagicMock, patch
from django.test import RequestFactory
from django.urls import reverse
from django.utils.timezone import now
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import ChallengeTypes
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.utils.time import timedelta_from_string
@ -17,6 +20,7 @@ from authentik.providers.oauth2.models import (
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD
class TestAuthorize(OAuthTestCase):
@ -302,40 +306,51 @@ class TestAuthorize(OAuthTestCase):
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
"client_id": "test",
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
token: RefreshToken = RefreshToken.objects.filter(user=user).first()
expires = timedelta_from_string(provider.access_code_validity).total_seconds()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": (
f"http://localhost#access_token={token.access_token}"
f"&id_token={provider.encode(token.id_token.to_dict())}&token_type=bearer"
f"&expires_in={int(expires)}&state={state}"
),
},
)
jwt = self.validate_jwt(token, provider)
self.assertAlmostEqual(
jwt["exp"] - now().timestamp(),
expires,
delta=5,
)
with patch(
"authentik.providers.oauth2.models.get_login_event",
MagicMock(
return_value=Event(
action=EventAction.LOGIN,
context={PLAN_CONTEXT_METHOD: "password"},
created=now(),
)
),
):
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
"client_id": "test",
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
token: RefreshToken = RefreshToken.objects.filter(user=user).first()
expires = timedelta_from_string(provider.access_code_validity).total_seconds()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": (
f"http://localhost#access_token={token.access_token}"
f"&id_token={provider.encode(token.id_token.to_dict())}&token_type=bearer"
f"&expires_in={int(expires)}&state={state}"
),
},
)
jwt = self.validate_jwt(token, provider)
self.assertEqual(jwt["amr"], ["pwd"])
self.assertAlmostEqual(
jwt["exp"] - now().timestamp(),
expires,
delta=5,
)
def test_full_form_post_id_token(self):
"""Test full authorization (form_post response)"""

View File

@ -27,6 +27,11 @@ class OAuthTestCase(TestCase):
cls.keypair = create_test_cert()
super().setUpClass()
def assert_non_none_or_unset(self, container: dict, key: str):
"""Check that a key, if set, is not none"""
if key in container:
self.assertIsNotNone(container[key])
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider) -> dict[str, Any]:
"""Validate that all required fields are set"""
key, alg = provider.jwt_key
@ -39,6 +44,10 @@ class OAuthTestCase(TestCase):
audience=provider.client_id,
)
id_token = token.id_token.to_dict()
self.assert_non_none_or_unset(id_token, "at_hash")
self.assert_non_none_or_unset(id_token, "nonce")
self.assert_non_none_or_unset(id_token, "c_hash")
self.assert_non_none_or_unset(id_token, "amr")
for key in self.required_jwt_keys:
self.assertIsNotNone(jwt[key], f"Key {key} is missing in access_token")
self.assertIsNotNone(id_token[key], f"Key {key} is missing in id_token")

View File

@ -17,7 +17,12 @@ from authentik.providers.oauth2.constants import (
GRANT_TYPE_REFRESH_TOKEN,
SCOPE_OPENID,
)
from authentik.providers.oauth2.models import OAuth2Provider, ResponseTypes, ScopeMapping
from authentik.providers.oauth2.models import (
OAuth2Provider,
ResponseMode,
ResponseTypes,
ScopeMapping,
)
from authentik.providers.oauth2.utils import cors_allow
LOGGER = get_logger()
@ -73,6 +78,11 @@ class ProviderInfoView(View):
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
],
"response_modes_supported": [
ResponseMode.QUERY,
ResponseMode.FRAGMENT,
ResponseMode.FORM_POST,
],
"jwks_uri": self.request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:jwks",

View File

@ -1,6 +1,7 @@
"""ProxyProvider API Views"""
from typing import Any, Optional
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, ReadOnlyField, SerializerMethodField
@ -39,22 +40,34 @@ class ProxyProviderSerializer(ProviderSerializer):
redirect_uris = CharField(read_only=True)
outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all")
def validate_basic_auth_enabled(self, value: bool) -> bool:
"""Ensure user and password attributes are set"""
if value:
if (
self.initial_data.get("basic_auth_password_attribute", "") == ""
or self.initial_data.get("basic_auth_user_attribute", "") == ""
):
raise ValidationError(
_("User and password attributes must be set when basic auth is enabled.")
)
return value
def validate(self, attrs) -> dict[Any, str]:
"""Check that internal_host is set when mode is Proxy"""
if (
attrs.get("mode", ProxyMode.PROXY) == ProxyMode.PROXY
and attrs.get("internal_host", "") == ""
):
raise ValidationError("Internal host cannot be empty when forward auth is disabled.")
raise ValidationError(_("Internal host cannot be empty when forward auth is disabled."))
return attrs
def create(self, validated_data):
def create(self, validated_data: dict):
instance: ProxyProvider = super().create(validated_data)
instance.set_oauth_defaults()
instance.save()
return instance
def update(self, instance: ProxyProvider, validated_data):
def update(self, instance: ProxyProvider, validated_data: dict):
instance = super().update(instance, validated_data)
instance.set_oauth_defaults()
instance.save()

View File

@ -0,0 +1,122 @@
"""proxy provider tests"""
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import ClientTypes
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
class ProxyProviderTests(APITestCase):
"""proxy provider tests"""
def setUp(self) -> None:
self.user = create_test_admin_user()
self.client.force_login(self.user)
def test_basic_auth(self):
"""Test basic_auth_enabled"""
response = self.client.post(
reverse("authentik_api:proxyprovider-list"),
{
"name": generate_id(),
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
"internal_host": "http://localhost",
"basic_auth_enabled": True,
"basic_auth_user_attribute": generate_id(),
"basic_auth_password_attribute": generate_id(),
},
)
self.assertEqual(response.status_code, 201)
def test_basic_auth_invalid(self):
"""Test basic_auth_enabled"""
response = self.client.post(
reverse("authentik_api:proxyprovider-list"),
{
"name": generate_id(),
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
"internal_host": "http://localhost",
"basic_auth_enabled": True,
},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{
"basic_auth_enabled": [
"User and password attributes must be set when basic auth is enabled."
]
},
)
def test_validate(self):
"""Test validate"""
response = self.client.post(
reverse("authentik_api:proxyprovider-list"),
{
"name": generate_id(),
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
},
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content.decode(),
{"non_field_errors": ["Internal host cannot be empty when forward auth is disabled."]},
)
def test_create_defaults(self):
"""Test create"""
name = generate_id()
response = self.client.post(
reverse("authentik_api:proxyprovider-list"),
{
"name": name,
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
"internal_host": "http://localhost",
},
)
self.assertEqual(response.status_code, 201)
provider: ProxyProvider = ProxyProvider.objects.get(name=name)
self.assertEqual(provider.client_type, ClientTypes.CONFIDENTIAL)
def test_update_defaults(self):
"""Test create"""
name = generate_id()
response = self.client.post(
reverse("authentik_api:proxyprovider-list"),
{
"name": name,
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
"internal_host": "http://localhost",
},
)
self.assertEqual(response.status_code, 201)
provider: ProxyProvider = ProxyProvider.objects.get(name=name)
self.assertEqual(provider.client_type, ClientTypes.CONFIDENTIAL)
provider.client_type = ClientTypes.PUBLIC
provider.save()
response = self.client.put(
reverse("authentik_api:proxyprovider-detail", kwargs={"pk": provider.pk}),
{
"name": name,
"mode": ProxyMode.PROXY,
"authorization_flow": create_test_flow().pk.hex,
"external_host": "http://localhost",
"internal_host": "http://localhost",
},
)
self.assertEqual(response.status_code, 200)
provider: ProxyProvider = ProxyProvider.objects.get(name=name)
self.assertEqual(provider.client_type, ClientTypes.CONFIDENTIAL)

View File

@ -47,6 +47,8 @@ class SAMLProviderSerializer(ProviderSerializer):
def get_url_download_metadata(self, instance: SAMLProvider) -> str:
"""Get metadata download URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
return request.build_absolute_uri(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": instance.pk}) + "?download"
@ -54,6 +56,8 @@ class SAMLProviderSerializer(ProviderSerializer):
def get_url_sso_post(self, instance: SAMLProvider) -> str:
"""Get SSO Post URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
@ -67,6 +71,8 @@ class SAMLProviderSerializer(ProviderSerializer):
def get_url_sso_redirect(self, instance: SAMLProvider) -> str:
"""Get SSO Redirect URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
@ -80,6 +86,8 @@ class SAMLProviderSerializer(ProviderSerializer):
def get_url_sso_init(self, instance: SAMLProvider) -> str:
"""Get SSO IDP-Initiated URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(

View File

@ -10,7 +10,7 @@ from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.events.signals import get_login_event
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.request_parser import AuthNRequest
@ -132,8 +132,8 @@ class AssertionProcessor:
auth_n_context, f"{{{NS_SAML_ASSERTION}}}AuthnContextClassRef"
)
auth_n_context_class_ref.text = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
if SESSION_LOGIN_EVENT in self.http_request.session:
event: Event = self.http_request.session[SESSION_LOGIN_EVENT]
event = get_login_event(self.http_request)
if event:
method = event.context.get(PLAN_CONTEXT_METHOD, "")
method_args = event.context.get(PLAN_CONTEXT_METHOD_ARGS, {})
if method == "password":

View File

@ -13,6 +13,7 @@ from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP,
NS_SAML_METADATA,
NS_SAML_PROTOCOL,
NS_SIGNATURE,
SAML_BINDING_POST,
SAML_BINDING_REDIRECT,
@ -35,7 +36,7 @@ class MetadataProcessor:
self.provider = provider
self.http_request = request
self.force_binding = None
self.xml_id = sha256(f"{provider.name}-{provider.pk}".encode("ascii")).hexdigest()
self.xml_id = "_" + sha256(f"{provider.name}-{provider.pk}".encode("ascii")).hexdigest()
def get_signing_key_descriptor(self) -> Optional[Element]:
"""Get Signing KeyDescriptor, if enabled for the provider"""
@ -143,9 +144,7 @@ class MetadataProcessor:
idp_sso_descriptor = SubElement(
entity_descriptor, f"{{{NS_SAML_METADATA}}}IDPSSODescriptor"
)
idp_sso_descriptor.attrib[
"protocolSupportEnumeration"
] = "urn:oasis:names:tc:SAML:2.0:protocol"
idp_sso_descriptor.attrib["protocolSupportEnumeration"] = NS_SAML_PROTOCOL
signing_descriptor = self.get_signing_key_descriptor()
if signing_descriptor is not None:

View File

@ -99,6 +99,9 @@ def worker_ready_hook(*args, **kwargs):
task.delay()
except ProgrammingError as exc:
LOGGER.warning("Startup task failed", task=task, exc=exc)
from authentik.blueprints.v1.tasks import start_blueprint_watcher
start_blueprint_watcher()
# Using a string here means the worker doesn't have to serialize

View File

@ -436,6 +436,7 @@ _LOGGING_HANDLER_MAP = {
"asyncio": "WARNING",
"redis": "WARNING",
"silk": "INFO",
"fsevents": "WARNING",
}
for handler_name, level in _LOGGING_HANDLER_MAP.items():
# pyright: reportGeneralTypeIssues=false

View File

@ -20,7 +20,7 @@ class CaptchaChallenge(WithUserInfoChallenge):
"""Site public key"""
site_key = CharField()
js_url = CharField(read_only=True)
js_url = CharField()
component = CharField(default="ak-stage-captcha")

View File

@ -22,7 +22,7 @@ class TestCaptchaStage(FlowTestCase):
self.user = create_test_admin_user()
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
self.stage = CaptchaStage.objects.create(
self.stage: CaptchaStage = CaptchaStage.objects.create(
name="captcha",
public_key=RECAPTCHA_PUBLIC_KEY,
private_key=RECAPTCHA_PRIVATE_KEY,
@ -41,3 +41,22 @@ class TestCaptchaStage(FlowTestCase):
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_urls(self):
"""Test URLs captcha"""
self.stage.js_url = "https://test.goauthentik.io/test.js"
self.stage.api_url = "https://test.goauthentik.io/test"
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
js_url="https://test.goauthentik.io/test.js",
)

View File

@ -12,7 +12,7 @@ class DummyStageSerializer(StageSerializer):
class Meta:
model = DummyStage
fields = StageSerializer.Meta.fields
fields = StageSerializer.Meta.fields + ["throw_error"]
class DummyStageViewSet(UsedByMixin, ModelViewSet):

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.4 on 2023-01-01 19:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_dummy", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="dummystage",
name="throw_error",
field=models.BooleanField(default=False),
),
]

View File

@ -1,5 +1,5 @@
"""dummy stage models"""
from django.db import models
from django.utils.translation import gettext as _
from django.views import View
from rest_framework.serializers import BaseSerializer
@ -10,6 +10,8 @@ from authentik.flows.models import Stage
class DummyStage(Stage):
"""Used for debugging."""
throw_error = models.BooleanField(default=False)
__debug_only__ = True
@property

View File

@ -4,6 +4,7 @@ from rest_framework.fields import CharField
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.stage import ChallengeStageView
from authentik.lib.sentry import SentryIgnoredException
class DummyChallenge(Challenge):
@ -27,6 +28,8 @@ class DummyStageView(ChallengeStageView):
return self.executor.stage_ok()
def get_challenge(self, *args, **kwargs) -> Challenge:
if self.executor.current_stage.throw_error:
raise SentryIgnoredException("Test error")
return DummyChallenge(
data={
"type": ChallengeTypes.NATIVE.value,

View File

@ -11,12 +11,11 @@ from django.utils.translation import gettext as _
from rest_framework.fields import CharField
from rest_framework.serializers import ValidationError
from authentik.core.models import User
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, SESSION_KEY_GET
from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -57,7 +56,7 @@ class EmailStageView(ChallengeStageView):
def get_token(self) -> FlowToken:
"""Get token"""
pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
pending_user = self.get_pending_user()
current_stage: EmailStage = self.executor.current_stage
valid_delta = timedelta(
minutes=current_stage.token_expiry + 1
@ -82,7 +81,7 @@ class EmailStageView(ChallengeStageView):
def send_email(self):
"""Helper function that sends the actual email. Implies that you've
already checked that there is a pending user."""
pending_user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
pending_user = self.get_pending_user()
email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None)
if not email:
email = pending_user.email
@ -104,13 +103,16 @@ class EmailStageView(ChallengeStageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify
if QS_KEY_TOKEN in request.session.get(
SESSION_KEY_GET, {}
) and self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED, False):
restore_token: FlowToken = self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED, None)
user = self.get_pending_user()
if restore_token:
if restore_token.user != user:
self.logger.warning("Flow token for non-matching user, denying request")
return self.executor.stage_invalid()
messages.success(request, _("Successfully verified Email."))
if self.executor.current_stage.activate_user_on_success:
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].is_active = True
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].save()
user.is_active = True
user.save()
return self.executor.stage_ok()
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
self.logger.debug("No pending user")

View File

@ -7,10 +7,9 @@ from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend
from django.urls import reverse
from django.utils.http import urlencode
from authentik.core.models import Token
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
@ -136,7 +135,7 @@ class TestEmailStage(FlowTestCase):
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
token: Token = Token.objects.get(user=self.user)
token: FlowToken = FlowToken.objects.get(user=self.user)
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
# Call the executor shell to preseed the session
@ -167,3 +166,43 @@ class TestEmailStage(FlowTestCase):
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(plan.context[PLAN_CONTEXT_PENDING_USER], self.user)
self.assertTrue(plan.context[PLAN_CONTEXT_PENDING_USER].is_active)
def test_token_invalid_user(self):
"""Test with token with invalid user"""
# Make sure token exists
self.test_pending_user()
self.user.is_active = False
self.user.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
# Set flow token user to a different user
token: FlowToken = FlowToken.objects.get(user=self.user)
token.user = create_test_admin_user()
token.save()
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", MagicMock()):
# Call the executor shell to preseed the session
url = reverse(
"authentik_api:flow-executor",
kwargs={"flow_slug": self.flow.slug},
)
url_query = urlencode(
{
QS_KEY_TOKEN: token.key,
}
)
url += f"?query={url_query}"
self.client.get(url)
# Call the actual executor to get the JSON Response
response = self.client.get(
reverse(
"authentik_api:flow-executor",
kwargs={"flow_slug": self.flow.slug},
)
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, component="ak-stage-access-denied")

View File

@ -1,7 +1,8 @@
"""email tests"""
from os import chmod, unlink
from pathlib import Path
from tempfile import gettempdir, mkstemp
from shutil import rmtree
from tempfile import mkdtemp, mkstemp
from typing import Any
from django.conf import settings
@ -20,11 +21,17 @@ def get_templates_setting(temp_dir: str) -> dict[str, Any]:
class TestEmailStageTemplates(TestCase):
"""Email tests"""
def setUp(self) -> None:
self.dir = mkdtemp()
def tearDown(self) -> None:
rmtree(self.dir)
def test_custom_template(self):
"""Test with custom template"""
with self.settings(TEMPLATES=get_templates_setting(gettempdir())):
_, file = mkstemp(suffix=".html")
_, file2 = mkstemp(suffix=".html")
with self.settings(TEMPLATES=get_templates_setting(self.dir)):
_, file = mkstemp(suffix=".html", dir=self.dir)
_, file2 = mkstemp(suffix=".html", dir=self.dir)
chmod(file2, 0o000) # Remove all permissions so we can't read the file
choices = get_template_choices()
self.assertEqual(choices[-1][0], Path(file).name)

View File

@ -7,14 +7,13 @@ from django.db.models.query import QuerySet
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import get_anonymous_user
from rest_framework.fields import BooleanField, CharField, ChoiceField, IntegerField, empty
from rest_framework.serializers import ValidationError
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.planner import FlowPlan
from authentik.flows.stage import ChallengeStageView
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBinding, PolicyBindingModel, PolicyEngineMode
@ -47,21 +46,23 @@ class PromptChallengeResponse(ChallengeResponse):
"""Validate response, fields are dynamically created based
on the stage"""
stage_instance: PromptStage
component = CharField(default="ak-stage-prompt")
def __init__(self, *args, **kwargs):
stage: PromptStage = kwargs.pop("stage", None)
stage: PromptStage = kwargs.pop("stage_instance", None)
plan: FlowPlan = kwargs.pop("plan", None)
request: HttpRequest = kwargs.pop("request", None)
user: User = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
self.stage = stage
self.stage_instance = stage
self.plan = plan
self.request = request
if not self.stage:
if not self.stage_instance:
return
# list() is called so we only load the fields once
fields = list(self.stage.fields.all())
fields = list(self.stage_instance.fields.all())
for field in fields:
field: Prompt
current = field.get_placeholder(
@ -97,7 +98,7 @@ class PromptChallengeResponse(ChallengeResponse):
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
# Check if we have any static or hidden fields, and ensure they
# still have the same value
static_hidden_fields: QuerySet[Prompt] = self.stage.fields.filter(
static_hidden_fields: QuerySet[Prompt] = self.stage_instance.fields.filter(
type__in=[FieldTypes.HIDDEN, FieldTypes.STATIC, FieldTypes.TEXT_READ_ONLY]
)
for static_hidden in static_hidden_fields:
@ -109,12 +110,17 @@ class PromptChallengeResponse(ChallengeResponse):
attrs[static_hidden.field_key] = default
# Check if we have two password fields, and make sure they are the same
password_fields: QuerySet[Prompt] = self.stage.fields.filter(type=FieldTypes.PASSWORD)
password_fields: QuerySet[Prompt] = self.stage_instance.fields.filter(
type=FieldTypes.PASSWORD
)
if password_fields.exists() and password_fields.count() == 2:
self._validate_password_fields(*[field.field_key for field in password_fields])
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
engine = ListPolicyEngine(self.stage.validation_policies.all(), user, self.request)
engine = ListPolicyEngine(
self.stage_instance.validation_policies.all(),
self.stage.get_pending_user(),
self.request,
)
engine.mode = PolicyEngineMode.MODE_ALL
engine.request.context[PLAN_CONTEXT_PROMPT] = attrs
engine.use_cache = False
@ -194,7 +200,8 @@ class PromptStageView(ChallengeStageView):
instance=None,
data=data,
request=self.request,
stage=self.executor.current_stage,
stage_instance=self.executor.current_stage,
stage=self,
plan=self.executor.plan,
user=self.get_pending_user(),
)

View File

@ -10,11 +10,15 @@ from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.flows.views.executor import SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.generators import generate_id
from authentik.policies.expression.models import ExpressionPolicy
from authentik.stages.prompt.models import FieldTypes, InlineFileField, Prompt, PromptStage
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT, PromptChallengeResponse
from authentik.stages.prompt.stage import (
PLAN_CONTEXT_PROMPT,
PromptChallengeResponse,
PromptStageView,
)
class TestPromptStage(FlowTestCase):
@ -106,6 +110,11 @@ class TestPromptStage(FlowTestCase):
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
self.request = RequestFactory().get("/")
self.request.user = create_test_admin_user()
self.flow_executor = FlowExecutorView(request=self.request)
self.stage_view = PromptStageView(self.flow_executor, request=self.request)
def test_inline_file_field(self):
"""test InlineFileField"""
with self.assertRaises(ValidationError):
@ -148,7 +157,11 @@ class TestPromptStage(FlowTestCase):
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None,
stage_instance=self.stage,
plan=plan,
data=self.prompt_data,
stage=self.stage_view,
)
self.assertEqual(challenge_response.is_valid(), True)
@ -160,7 +173,7 @@ class TestPromptStage(FlowTestCase):
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), False)
@ -180,7 +193,7 @@ class TestPromptStage(FlowTestCase):
self.stage.validation_policies.set([expr_policy])
self.stage.save()
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), True)
@ -208,7 +221,7 @@ class TestPromptStage(FlowTestCase):
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
self.prompt_data["password2_prompt"] = "qwerqwerqr"
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), False)
self.assertEqual(
@ -222,7 +235,7 @@ class TestPromptStage(FlowTestCase):
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
self.prompt_data["username_prompt"] = user.username
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), False)
self.assertEqual(
@ -237,7 +250,7 @@ class TestPromptStage(FlowTestCase):
self.prompt_data["hidden_prompt"] = "foo"
self.prompt_data["static_prompt"] = "foo"
challenge_response = PromptChallengeResponse(
None, stage=self.stage, plan=plan, data=self.prompt_data
None, stage_instance=self.stage, plan=plan, data=self.prompt_data, stage=self.stage_view
)
self.assertEqual(challenge_response.is_valid(), True)
self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo")

View File

@ -73,6 +73,9 @@ class TestUserWriteStage(FlowTestCase):
"username": "test-user-new",
"password": new_password,
"attributes.some.custom-attribute": "test",
"attributes": {
"foo": "bar",
},
"some_ignored_attribute": "bar",
}
session = self.client.session
@ -89,6 +92,7 @@ class TestUserWriteStage(FlowTestCase):
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(new_password))
self.assertEqual(user_qs.first().attributes["some"]["custom-attribute"], "test")
self.assertEqual(user_qs.first().attributes["foo"], "bar")
self.assertNotIn("some_ignored_attribute", user_qs.first().attributes)
@patch(

View File

@ -5,12 +5,14 @@ from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import SecretKeyFilter
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG
@ -109,6 +111,8 @@ class TenantViewSet(UsedByMixin, ModelViewSet):
]
ordering = ["domain"]
filter_backends = [SecretKeyFilter, OrderingFilter, SearchFilter]
@extend_schema(
responses=CurrentTenantSerializer(many=False),
)

View File

@ -154,6 +154,7 @@ entries:
policy: !KeyOf default-recovery-skip-if-restored
target: !KeyOf flow-binding-email
order: 0
state: absent
model: authentik_policies.policybinding
attrs:
negate: false

View File

@ -61,7 +61,6 @@
"authentik_events.notificationwebhookmapping",
"authentik_flows.flow",
"authentik_flows.flowstagebinding",
"authentik_flows.flowtoken",
"authentik_outposts.dockerserviceconnection",
"authentik_outposts.kubernetesserviceconnection",
"authentik_outposts.outpost",

View File

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

4
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/jellydator/ttlcache/v3 v3.0.0
github.com/jellydator/ttlcache/v3 v3.0.1
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
github.com/pires/go-proxyproto v0.6.2
@ -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.2022114.2
goauthentik.io/api/v3 v3.2022121.4
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

8
go.sum
View File

@ -232,8 +232,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jellydator/ttlcache/v3 v3.0.0 h1:zmFhqrB/4sKiEiJHhtseJsNRE32IMVmJSs4++4gaQO4=
github.com/jellydator/ttlcache/v3 v3.0.0/go.mod h1:WwTaEmcXQ3MTjOm4bsZoDFiCu/hMvNWLO1w67RXz6h4=
github.com/jellydator/ttlcache/v3 v3.0.1 h1:cHgCSMS7TdQcoprXnWUptJZzyFsqs18Lt8VVhRuZYVU=
github.com/jellydator/ttlcache/v3 v3.0.1/go.mod h1:WwTaEmcXQ3MTjOm4bsZoDFiCu/hMvNWLO1w67RXz6h4=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -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.2022114.2 h1:C2S6XuKa6X/3ZEdu9LwaMML8lvEMf/d2Ktr+yzOexNs=
goauthentik.io/api/v3 v3.2022114.2/go.mod h1:QM9J32HgYE4gL71lWAfAoXSPdSmLVLW08itfLI3Mo10=
goauthentik.io/api/v3 v3.2022121.4 h1:WCwbjXB6Q2VkASkgNc2SFaW36tEBhvOR2tIzmSnheYk=
goauthentik.io/api/v3 v3.2022121.4/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 = "2022.12.0"
const VERSION = "2022.12.3"

View File

@ -167,6 +167,19 @@ func (a *APIController) OnRefresh() error {
return err
}
func (a *APIController) getWebsocketArgs() map[string]interface{} {
args := map[string]interface{}{
"version": constants.VERSION,
"buildHash": constants.BUILD("tagged"),
"uuid": a.instanceUUID.String(),
}
hostname, err := os.Hostname()
if err == nil {
args["hostname"] = hostname
}
return args
}
func (a *APIController) StartBackgroundTasks() error {
OutpostInfo.With(prometheus.Labels{
"outpost_name": a.Outpost.Name,

View File

@ -49,11 +49,7 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
// Send hello message with our version
msg := websocketMessage{
Instruction: WebsocketInstructionHello,
Args: map[string]interface{}{
"version": constants.VERSION,
"buildHash": constants.BUILD("tagged"),
"uuid": ac.instanceUUID.String(),
},
Args: ac.getWebsocketArgs(),
}
err = ws.WriteJSON(msg)
if err != nil {
@ -163,11 +159,7 @@ func (ac *APIController) startWSHealth() {
for ; true; <-ticker.C {
aliveMsg := websocketMessage{
Instruction: WebsocketInstructionHello,
Args: map[string]interface{}{
"version": constants.VERSION,
"buildHash": constants.BUILD("tagged"),
"uuid": ac.instanceUUID.String(),
},
Args: ac.getWebsocketArgs(),
}
if ac.wsConn == nil {
go ac.reconnectWS()

View File

@ -34,6 +34,7 @@ type ProviderInstance struct {
tlsServerName *string
cert *tls.Certificate
certUUID string
outpostName string
outpostPk int32
searchAllowedGroups []*strfmt.UUID

View File

@ -15,6 +15,7 @@ func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certifica
return ls.providers[0].cert, nil
}
}
allIdenticalCerts := true
for _, provider := range ls.providers {
if provider.tlsServerName == &info.ServerName {
if provider.cert == nil {
@ -23,6 +24,13 @@ func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certifica
}
return provider.cert, nil
}
if provider.certUUID != ls.providers[0].certUUID || provider.cert == nil {
allIdenticalCerts = false
}
}
if allIdenticalCerts {
ls.log.WithField("server-name", info.ServerName).Debug("all providers have the same keypair, using keypair")
return ls.providers[0].cert, nil
}
ls.log.WithField("server-name", info.ServerName).Debug("Fallback to default cert")
return ls.defaultCert, nil

View File

@ -70,13 +70,13 @@ func (ls *LDAPServer) Refresh() error {
outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk,
}
if provider.Certificate.Get() != nil {
kp := provider.Certificate.Get()
if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp)
if err != nil {
ls.log.WithError(err).Warning("Failed to initially fetch certificate")
}
providers[idx].cert = ls.cs.Get(*kp)
providers[idx].certUUID = *kp
}
if *provider.SearchMode.Ptr() == api.LDAPAPIACCESSMODE_CACHED {
providers[idx].searcher = memorysearch.NewMemorySearcher(providers[idx])

185
poetry.lock generated
View File

@ -454,7 +454,7 @@ python-versions = "*"
[[package]]
name = "coverage"
version = "7.0.1"
version = "7.0.3"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@ -552,7 +552,7 @@ graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "django"
version = "4.1.4"
version = "4.1.5"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
@ -906,7 +906,7 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "5.2.0"
version = "6.0.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
@ -1158,14 +1158,11 @@ attrs = ">=19.2.0"
[[package]]
name = "packaging"
version = "21.3"
version = "22.0"
description = "Core utilities for Python packages"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
python-versions = ">=3.7"
[[package]]
name = "paramiko"
@ -1407,17 +1404,6 @@ cryptography = ">=38.0.0,<39"
docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"]
test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "main"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyrsistent"
version = "0.19.2"
@ -1991,6 +1977,17 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "watchdog"
version = "2.2.1"
description = "Filesystem events monitoring"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "watchfiles"
version = "0.18.1"
@ -2127,7 +2124,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.11"
content-hash = "7a0fe2bd1d710517a961731f78a2cf2e9d70c277d208606c56d765947e529dca"
content-hash = "47eeb02200cb4980368d3a11c6bee111a19a86d7e4d8ad90ef3bd590493f28b3"
[metadata.files]
aiohttp = [
@ -2464,57 +2461,57 @@ constantly = [
{file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"},
]
coverage = [
{file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"},
{file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"},
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"},
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"},
{file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"},
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"},
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"},
{file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"},
{file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"},
{file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"},
{file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"},
{file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"},
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"},
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"},
{file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"},
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"},
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"},
{file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"},
{file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"},
{file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"},
{file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"},
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"},
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"},
{file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"},
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"},
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"},
{file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"},
{file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"},
{file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"},
{file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"},
{file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"},
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"},
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"},
{file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"},
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"},
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"},
{file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"},
{file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"},
{file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"},
{file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"},
{file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"},
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"},
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"},
{file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"},
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"},
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"},
{file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"},
{file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"},
{file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"},
{file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"},
{file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"},
{file = "coverage-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f7c51b6074a8a3063c341953dffe48fd6674f8e4b1d3c8aa8a91f58d6e716a8"},
{file = "coverage-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:628f47eaf66727fc986d3b190d6fa32f5e6b7754a243919d28bc0fd7974c449f"},
{file = "coverage-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89d5abf86c104de808108a25d171ad646c07eda96ca76c8b237b94b9c71e518"},
{file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75e43c6f4ea4d122dac389aabdf9d4f0e160770a75e63372f88005d90f5bcc80"},
{file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49da0ff241827ebb52d5d6d5a36d33b455fa5e721d44689c95df99fd8db82437"},
{file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0bce4ad5bdd0b02e177a085d28d2cea5fc57bb4ba2cead395e763e34cf934eb1"},
{file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f79691335257d60951638dd43576b9bcd6f52baa5c1c2cd07a509bb003238372"},
{file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5722269ed05fbdb94eef431787c66b66260ff3125d1a9afcc00facff8c45adf9"},
{file = "coverage-7.0.3-cp310-cp310-win32.whl", hash = "sha256:bdbda870e0fda7dd0fe7db7135ca226ec4c1ade8aa76e96614829b56ca491012"},
{file = "coverage-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:e56fae4292e216b8deeee38ace84557b9fa85b52db005368a275427cdabb8192"},
{file = "coverage-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b82343a5bc51627b9d606f0b6b6b9551db7b6311a5dd920fa52a94beae2e8959"},
{file = "coverage-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd0a8aa431f9b7ad9eb8264f55ef83cbb254962af3775092fb6e93890dea9ca2"},
{file = "coverage-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112cfead1bd22eada8a8db9ed387bd3e8be5528debc42b5d3c1f7da4ffaf9fb5"},
{file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af87e906355fa42447be5c08c5d44e6e1c005bf142f303f726ddf5ed6e0c8a4d"},
{file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f30090e22a301952c5abd0e493a1c8358b4f0b368b49fa3e4568ed3ed68b8d1f"},
{file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae871d09901911eedda1981ea6fd0f62a999107293cdc4c4fd612321c5b34745"},
{file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ed7c9debf7bfc63c9b9f8b595409237774ff4b061bf29fba6f53b287a2fdeab9"},
{file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13121fa22dcd2c7b19c5161e3fd725692448f05377b788da4502a383573227b3"},
{file = "coverage-7.0.3-cp311-cp311-win32.whl", hash = "sha256:037b51ee86bc600f99b3b957c20a172431c35c2ef9c1ca34bc813ab5b51fd9f5"},
{file = "coverage-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:25fde928306034e8deecd5fc91a07432dcc282c8acb76749581a28963c9f4f3f"},
{file = "coverage-7.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e8b0642c38b3d3b3c01417643ccc645345b03c32a2e84ef93cdd6844d6fe530"},
{file = "coverage-7.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18b09811f849cc958d23f733a350a66b54a8de3fed1e6128ba55a5c97ffb6f65"},
{file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:349d0b545520e8516f7b4f12373afc705d17d901e1de6a37a20e4ec9332b61f7"},
{file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b38813eee5b4739f505d94247604c72eae626d5088a16dd77b08b8b1724ab3"},
{file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba9af1218fa01b1f11c72271bc7290b701d11ad4dbc2ae97c445ecacf6858dba"},
{file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c5648c7eec5cf1ba5db1cf2d6c10036a582d7f09e172990474a122e30c841361"},
{file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0df04495b76a885bfef009f45eebe8fe2fbf815ad7a83dabcf5aced62f33162"},
{file = "coverage-7.0.3-cp37-cp37m-win32.whl", hash = "sha256:af6cef3796b8068713a48dd67d258dc9a6e2ebc3bd4645bfac03a09672fa5d20"},
{file = "coverage-7.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:62ef3800c4058844e2e3fa35faa9dd0ccde8a8aba6c763aae50342e00d4479d4"},
{file = "coverage-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acef7f3a3825a2d218a03dd02f5f3cc7f27aa31d882dd780191d1ad101120d74"},
{file = "coverage-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a530663a361eb27375cec28aea5cd282089b5e4b022ae451c4c3493b026a68a5"},
{file = "coverage-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c58cd6bb46dcb922e0d5792850aab5964433d511b3a020867650f8d930dde4f4"},
{file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f918e9ef4c98f477a5458238dde2a1643aed956c7213873ab6b6b82e32b8ef61"},
{file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b865aa679bee7fbd1c55960940dbd3252621dd81468268786c67122bbd15343"},
{file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c5d9b480ebae60fc2cbc8d6865194136bc690538fa542ba58726433bed6e04cc"},
{file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:985ad2af5ec3dbb4fd75d5b0735752c527ad183455520055a08cf8d6794cabfc"},
{file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ca15308ef722f120967af7474ba6a453e0f5b6f331251e20b8145497cf1bc14a"},
{file = "coverage-7.0.3-cp38-cp38-win32.whl", hash = "sha256:c1cee10662c25c94415bbb987f2ec0e6ba9e8fce786334b10be7e6a7ab958f69"},
{file = "coverage-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:44d6a556de4418f1f3bfd57094b8c49f0408df5a433cf0d253eeb3075261c762"},
{file = "coverage-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e6dcc70a25cb95df0ae33dfc701de9b09c37f7dd9f00394d684a5b57257f8246"},
{file = "coverage-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf76d79dfaea802f0f28f50153ffbc1a74ae1ee73e480baeda410b4f3e7ab25f"},
{file = "coverage-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88834e5d56d01c141c29deedacba5773fe0bed900b1edc957595a8a6c0da1c3c"},
{file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef001a60e888f8741e42e5aa79ae55c91be73761e4df5e806efca1ddd62fd400"},
{file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959dc506be74e4963bd2c42f7b87d8e4b289891201e19ec551e64c6aa5441f8"},
{file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b791beb17b32ac019a78cfbe6184f992b6273fdca31145b928ad2099435e2fcb"},
{file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b07651e3b9af8f1a092861d88b4c74d913634a7f1f2280fca0ad041ad84e9e96"},
{file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e46fa4168ccb7497c9be78627fcb147e06f474f846a10d55feeb5108a24ef0"},
{file = "coverage-7.0.3-cp39-cp39-win32.whl", hash = "sha256:e3f1cd1cd65695b1540b3cf7828d05b3515974a9d7c7530f762ac40f58a18161"},
{file = "coverage-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:d8249666c23683f74f8f93aeaa8794ac87cc61c40ff70374a825f3352a4371dc"},
{file = "coverage-7.0.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:b1ffc8f58b81baed3f8962e28c30d99442079b82ce1ec836a1f67c0accad91c1"},
{file = "coverage-7.0.3.tar.gz", hash = "sha256:d5be4e93acce64f516bf4fd239c0e6118fc913c93fa1a3f52d15bdcc60d97b2d"},
]
cryptography = [
{file = "cryptography-38.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320"},
@ -2569,8 +2566,8 @@ dill = [
{file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
]
django = [
{file = "Django-4.1.4-py3-none-any.whl", hash = "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148"},
{file = "Django-4.1.4.tar.gz", hash = "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b"},
{file = "Django-4.1.5-py3-none-any.whl", hash = "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763"},
{file = "Django-4.1.5.tar.gz", hash = "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef"},
]
django-filter = [
{file = "django-filter-22.1.tar.gz", hash = "sha256:ed473b76e84f7e83b2511bb2050c3efb36d135207d0128dfe3ae4b36e3594ba5"},
@ -2795,8 +2792,8 @@ idna = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
importlib-metadata = [
{file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"},
{file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"},
{file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
{file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
]
incremental = [
{file = "incremental-22.10.0-py2.py3-none-any.whl", hash = "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"},
@ -3114,8 +3111,8 @@ outcome = [
{file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
{file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"},
{file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"},
]
paramiko = [
{file = "paramiko-2.12.0-py2.py3-none-any.whl", hash = "sha256:b2df1a6325f6996ef55a8789d0462f5b502ea83b3c990cbb5bbe57345c6812c4"},
@ -3332,10 +3329,6 @@ pyopenssl = [
{file = "pyOpenSSL-22.1.0-py3-none-any.whl", hash = "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"},
{file = "pyOpenSSL-22.1.0.tar.gz", hash = "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pyrsistent = [
{file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"},
{file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"},
@ -3634,6 +3627,36 @@ vine = [
{file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
{file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"},
]
watchdog = [
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"},
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"},
{file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"},
{file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"},
{file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"},
{file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"},
{file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"},
{file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"},
{file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"},
{file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"},
{file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"},
{file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"},
{file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"},
{file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"},
{file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"},
{file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"},
{file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"},
{file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"},
{file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"},
{file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"},
{file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"},
{file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"},
]
watchfiles = [
{file = "watchfiles-0.18.1-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:9891d3c94272108bcecf5597a592e61105279def1313521e637f2d5acbe08bc9"},
{file = "watchfiles-0.18.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7102342d60207fa635e24c02a51c6628bf0472e5fef067f78a612386840407fc"},

View File

@ -100,7 +100,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
[tool.poetry]
name = "authentik"
version = "2022.12.0"
version = "2022.12.3"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]
@ -155,6 +155,7 @@ webauthn = "*"
wsproto = "*"
xmlsec = "*"
zxcvbn = "*"
watchdog = "*"
[tool.poetry.dev-dependencies]
bandit = "*"

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2022.12.0
version: 2022.12.3
description: Making authentication simple.
contact:
email: hello@goauthentik.io
@ -3578,60 +3578,78 @@ paths:
operationId: core_tenants_list
description: Tenant Viewset
parameters:
- in: query
name: branding_favicon
- name: branding_favicon
required: false
in: query
description: branding_favicon
schema:
type: string
- in: query
name: branding_logo
- name: branding_logo
required: false
in: query
description: branding_logo
schema:
type: string
- in: query
name: branding_title
- name: branding_title
required: false
in: query
description: branding_title
schema:
type: string
- in: query
name: default
schema:
type: boolean
- in: query
name: domain
- name: default
required: false
in: query
description: default
schema:
type: string
- in: query
name: event_retention
- name: domain
required: false
in: query
description: domain
schema:
type: string
- in: query
name: flow_authentication
- name: event_retention
required: false
in: query
description: event_retention
schema:
type: string
format: uuid
- in: query
name: flow_device_code
- name: flow_authentication
required: false
in: query
description: flow_authentication
schema:
type: string
format: uuid
- in: query
name: flow_invalidation
- name: flow_device_code
required: false
in: query
description: flow_device_code
schema:
type: string
format: uuid
- in: query
name: flow_recovery
- name: flow_invalidation
required: false
in: query
description: flow_invalidation
schema:
type: string
format: uuid
- in: query
name: flow_unenrollment
- name: flow_recovery
required: false
in: query
description: flow_recovery
schema:
type: string
format: uuid
- in: query
name: flow_user_settings
- name: flow_unenrollment
required: false
in: query
description: flow_unenrollment
schema:
type: string
- name: flow_user_settings
required: false
in: query
description: flow_user_settings
schema:
type: string
format: uuid
- name: ordering
required: false
in: query
@ -3656,16 +3674,18 @@ paths:
description: A search term.
schema:
type: string
- in: query
name: tenant_uuid
- name: tenant_uuid
required: false
in: query
description: tenant_uuid
schema:
type: string
format: uuid
- in: query
name: web_certificate
- name: web_certificate
required: false
in: query
description: web_certificate
schema:
type: string
format: uuid
tags:
- core
security:
@ -5011,12 +5031,16 @@ paths:
schema:
type: boolean
default: true
- in: query
name: managed
- name: managed
required: false
in: query
description: managed
schema:
type: string
- in: query
name: name
- name: name
required: false
in: query
description: name
schema:
type: string
- name: ordering
@ -21582,6 +21606,10 @@ paths:
schema:
type: string
format: uuid
- in: query
name: throw_error
schema:
type: boolean
tags:
- stages
security:
@ -26333,7 +26361,6 @@ components:
type: string
js_url:
type: string
readOnly: true
required:
- js_url
- pending_user
@ -26583,7 +26610,7 @@ components:
ak-stage-consent: '#/components/schemas/ConsentChallenge'
ak-stage-dummy: '#/components/schemas/DummyChallenge'
ak-stage-email: '#/components/schemas/EmailChallenge'
xak-flow-error: '#/components/schemas/FlowErrorChallenge'
ak-stage-flow-error: '#/components/schemas/FlowErrorChallenge'
ak-stage-identification: '#/components/schemas/IdentificationChallenge'
ak-provider-oauth2-device-code: '#/components/schemas/OAuthDeviceCodeChallenge'
ak-provider-oauth2-device-code-finish: '#/components/schemas/OAuthDeviceCodeFinishChallenge'
@ -27153,6 +27180,8 @@ components:
type: array
items:
$ref: '#/components/schemas/FlowSet'
throw_error:
type: boolean
required:
- component
- meta_model_name
@ -27171,6 +27200,8 @@ components:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
throw_error:
type: boolean
required:
- name
DuoDevice:
@ -27844,22 +27875,19 @@ components:
are shown an error message, superusers are shown a full stacktrace.
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
type: string
default: native
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: xak-flow-error
default: ak-stage-flow-error
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
request_id:
type: string
error:
@ -27867,10 +27895,7 @@ components:
traceback:
type: string
required:
- pending_user
- pending_user_avatar
- request_id
- type
FlowImportResult:
type: object
description: Logs of an attempted flow import
@ -30179,6 +30204,9 @@ components:
type: object
description: Outpost health status
properties:
uid:
type: string
readOnly: true
last_seen:
type: string
format: date-time
@ -30198,10 +30226,15 @@ components:
build_hash_should:
type: string
readOnly: true
hostname:
type: string
readOnly: true
required:
- build_hash
- build_hash_should
- hostname
- last_seen
- uid
- version
- version_outdated
- version_should
@ -33707,6 +33740,8 @@ components:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
throw_error:
type: boolean
PatchedDuoDeviceRequest:
type: object
description: Serializer for Duo authenticator devices
@ -37905,6 +37940,7 @@ components:
- username
UserAccountRequest:
type: object
description: Account adding/removing operations
properties:
pk:
type: integer

View File

@ -47,7 +47,6 @@ class TestProviderLDAP(SeleniumTestCase):
def _prepare(self) -> User:
"""prepare user, provider, app and container"""
# set additionalHeaders to test later
self.user.attributes["extraAttribute"] = "bar"
self.user.save()

View File

@ -1,4 +1,5 @@
"""Proxy and Outpost e2e tests"""
from base64 import b64encode
from dataclasses import asdict
from sys import platform
from time import sleep
@ -14,6 +15,7 @@ from authentik import __version__
from authentik.blueprints.tests import apply_blueprint, reconcile_app
from authentik.core.models import Application
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
from authentik.outposts.tasks import outpost_local_connection
from authentik.providers.proxy.models import ProxyProvider
@ -119,6 +121,78 @@ class TestProviderProxy(SeleniumTestCase):
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
self.assertIn("You've logged out of proxy.", full_body_text)
@retry()
@apply_blueprint(
"default/10-flow-default-authentication-flow.yaml",
"default/10-flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/20-flow-default-provider-authorization-explicit-consent.yaml",
"default/20-flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint(
"system/providers-oauth2.yaml",
"system/providers-proxy.yaml",
)
@reconcile_app("authentik_crypto")
def test_proxy_basic_auth(self):
"""Test simple outpost setup with single provider"""
cred = generate_id()
attr = "basic-password" # nosec
self.user.attributes["basic-username"] = cred
self.user.attributes[attr] = cred
self.user.save()
proxy: ProxyProvider = ProxyProvider.objects.create(
name="proxy_provider",
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
internal_host="http://localhost",
external_host="http://localhost:9000",
basic_auth_enabled=True,
basic_auth_user_attribute="basic-username",
basic_auth_password_attribute=attr,
)
# Ensure OAuth2 Params are set
proxy.set_oauth_defaults()
proxy.save()
# we need to create an application to actually access the proxy
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
outpost: Outpost = Outpost.objects.create(
name="proxy_outpost",
type=OutpostType.PROXY,
)
outpost.providers.add(proxy)
outpost.build_user_permissions(outpost.user)
self.proxy_container = self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50:
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
self.driver.get("http://localhost:9000")
self.login()
sleep(1)
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
self.assertIn(f"X-Authentik-Username: {self.user.username}", full_body_text)
auth_header = b64encode(f"{cred}:{cred}".encode()).decode()
self.assertIn(f"Authorization: Basic {auth_header}", full_body_text)
self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out")
sleep(2)
full_body_text = self.driver.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text
self.assertIn("You've logged out of proxy.", full_body_text)
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderProxyConnect(ChannelsLiveServerTestCase):

312
web/package-lock.json generated
View File

@ -21,7 +21,8 @@
"@codemirror/legacy-modes": "^6.3.1",
"@formatjs/intl-listformat": "^7.1.7",
"@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2022.11.4-1672221153",
"@goauthentik/api": "^2022.12.1-1672612412",
"@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.15.0",
"@lingui/core": "^3.15.0",
@ -37,13 +38,13 @@
"@rollup/plugin-typescript": "^10.0.1",
"@sentry/browser": "^7.28.1",
"@sentry/tracing": "^7.28.1",
"@squoosh/cli": "^0.7.2",
"@squoosh/cli": "^0.7.3",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.5",
"@types/codemirror": "5.60.6",
"@types/grecaptcha": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3",
@ -53,16 +54,16 @@
"codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0",
"country-flag-icons": "^1.5.5",
"eslint": "^8.30.0",
"eslint": "^8.31.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.6",
"eslint-plugin-custom-elements": "0.0.7",
"eslint-plugin-lit": "^1.7.2",
"fuse.js": "^6.6.2",
"lit": "^2.5.0",
"mermaid": "^9.3.0",
"moment": "^2.29.4",
"prettier": "^2.8.1",
"pyright": "^1.1.285",
"pyright": "^1.1.286",
"rapidoc": "^9.3.3",
"rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0",
@ -71,9 +72,10 @@
"rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1",
"tslib": "^2.4.1",
"turnstile-types": "^1.0.2",
"typescript": "^4.9.4",
"webcomponent-qr-code": "^1.1.0",
"yaml": "^2.2.0"
"yaml": "^2.2.1"
}
},
"node_modules/@ampproject/remapping": {
@ -1874,9 +1876,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz",
"integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
"integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@ -1957,9 +1959,14 @@
}
},
"node_modules/@goauthentik/api": {
"version": "2022.11.4-1672221153",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.11.4-1672221153.tgz",
"integrity": "sha512-BuKEusVBpX+ggIfFDK8/4ThsZ8ld6q4F5lqXcmKVCtVtxsSJKuAmSgEvudqV0sCMpXttaWbed+rodskgc3p3ng=="
"version": "2022.12.1-1672612412",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.12.1-1672612412.tgz",
"integrity": "sha512-fyAk76avWt0KzBfcrUPPopwRW2KWw/yo+MYva3ocWcU+4dbYlwEiT3LehToPMOOHoM81vRbyP9oVE5f472TyUQ=="
},
"node_modules/@hcaptcha/types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz",
"integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
@ -3047,9 +3054,9 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@squoosh/cli": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.2.tgz",
"integrity": "sha512-uMnUWMx4S8UApO/EfPyRyvUmw+0jI9wwAfdHfGjvVg4DAIvEgsA+VWK2KOBnJiChvVd768K27g09ESzptyX93w==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.3.tgz",
"integrity": "sha512-mU/iWbVWqLXX+gJJa4RBl5U4LdKYaiD9cmxW7bjyw8EEFAuMXFDGAQcq7hEvUZwoP5Em5s7sjimBPirSBJQ87g==",
"dependencies": {
"@squoosh/lib": "^0.4.0",
"commander": "^7.2.0",
@ -3234,9 +3241,9 @@
}
},
"node_modules/@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"version": "5.60.6",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.6.tgz",
"integrity": "sha512-JIDPSvkYRlcv/2F0erqD+de2ni/Mz6FJMEGb0vwF6ByQOcHIKfiEfwrO4d6dSRwYeHyNUMpGjev0PyjX2M0XWw==",
"dependencies": {
"@types/tern": "*"
}
@ -3382,13 +3389,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
"integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
"integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/type-utils": "5.47.1",
"@typescript-eslint/utils": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/type-utils": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@ -3428,13 +3435,13 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.1.tgz",
"integrity": "sha512-9Vb+KIv29r6GPu4EboWOnQM7T+UjpjXvjCPhNORlgm40a9Ia9bvaPJswvtae1gip2QEeVeGh6YquqAzEgoRAlw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"debug": "^4.3.4"
},
"engines": {
@ -3454,12 +3461,12 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3470,12 +3477,12 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
"integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
"integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
"dependencies": {
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/utils": "5.47.1",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -3496,9 +3503,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -3508,12 +3515,12 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -3548,15 +3555,15 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
"integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
"integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@ -3587,11 +3594,11 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/types": "5.48.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -5181,11 +5188,11 @@
}
},
"node_modules/eslint": {
"version": "8.30.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz",
"integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==",
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
"dependencies": {
"@eslint/eslintrc": "^1.4.0",
"@eslint/eslintrc": "^1.4.1",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@ -5247,9 +5254,9 @@
}
},
"node_modules/eslint-plugin-custom-elements": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.6.tgz",
"integrity": "sha512-JwPHRSOUe7y8dpC5hg90ySHejsfnQ3yqprv0902VMZ3j8FRZDudj+yzxqqkRDhZTNFUxP3r+0TWuveZhLgJONg==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.7.tgz",
"integrity": "sha512-s37mXujv/fAsPlvoLbO8jiZDM2gc/dFXIDf7wK52LMrsTsLpE5ITugQCwPM8lmcONjYdswS5kneX9LzJlt7aTA==",
"peerDependencies": {
"eslint": ">=4.19.0"
}
@ -6867,9 +6874,9 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE="
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
"bin": {
"json5": "lib/cli.js"
},
@ -8138,9 +8145,9 @@
}
},
"node_modules/pyright": {
"version": "1.1.285",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.285.tgz",
"integrity": "sha512-z+KJyaguJyVfEVxzVlyannjYtD13LGMdGTcQJuiq975f1nTHZ4oSwd9SrAZWP+rDHW/NVQCOz0CBlsMeNA/2sg==",
"version": "1.1.286",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.286.tgz",
"integrity": "sha512-67+zViU+bN8m6cZQQc65aSinEguuFDI6gn2yf+naMjXuR5s65sAHAjP2FcPnd/AeB+F9fq4oytJ8UQOAbhLhFg==",
"bin": {
"pyright": "index.js",
"pyright-langserver": "langserver.index.js"
@ -9511,6 +9518,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/turnstile-types": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/turnstile-types/-/turnstile-types-1.0.2.tgz",
"integrity": "sha512-Y98xYhxf9xtYuu1QCrQm7og0o2zw2bGdsyUNXeWgPVXsHFkJIwTrRY6o3Oioa9PjFtoYM+oHe1n23V+oE27WUQ=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -9961,9 +9973,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
"integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==",
"engines": {
"node": ">= 14"
}
@ -11333,9 +11345,9 @@
}
},
"@eslint/eslintrc": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz",
"integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
"integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@ -11396,9 +11408,14 @@
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
},
"@goauthentik/api": {
"version": "2022.11.4-1672221153",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.11.4-1672221153.tgz",
"integrity": "sha512-BuKEusVBpX+ggIfFDK8/4ThsZ8ld6q4F5lqXcmKVCtVtxsSJKuAmSgEvudqV0sCMpXttaWbed+rodskgc3p3ng=="
"version": "2022.12.1-1672612412",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2022.12.1-1672612412.tgz",
"integrity": "sha512-fyAk76avWt0KzBfcrUPPopwRW2KWw/yo+MYva3ocWcU+4dbYlwEiT3LehToPMOOHoM81vRbyP9oVE5f472TyUQ=="
},
"@hcaptcha/types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz",
"integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ=="
},
"@humanwhocodes/config-array": {
"version": "0.11.8",
@ -12217,9 +12234,9 @@
}
},
"@squoosh/cli": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.2.tgz",
"integrity": "sha512-uMnUWMx4S8UApO/EfPyRyvUmw+0jI9wwAfdHfGjvVg4DAIvEgsA+VWK2KOBnJiChvVd768K27g09ESzptyX93w==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@squoosh/cli/-/cli-0.7.3.tgz",
"integrity": "sha512-mU/iWbVWqLXX+gJJa4RBl5U4LdKYaiD9cmxW7bjyw8EEFAuMXFDGAQcq7hEvUZwoP5Em5s7sjimBPirSBJQ87g==",
"requires": {
"@squoosh/lib": "^0.4.0",
"commander": "^7.2.0",
@ -12368,9 +12385,9 @@
}
},
"@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"version": "5.60.6",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.6.tgz",
"integrity": "sha512-JIDPSvkYRlcv/2F0erqD+de2ni/Mz6FJMEGb0vwF6ByQOcHIKfiEfwrO4d6dSRwYeHyNUMpGjev0PyjX2M0XWw==",
"requires": {
"@types/tern": "*"
}
@ -12515,13 +12532,13 @@
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
},
"@typescript-eslint/eslint-plugin": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
"integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
"integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
"requires": {
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/type-utils": "5.47.1",
"@typescript-eslint/utils": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/type-utils": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@ -12541,48 +12558,48 @@
}
},
"@typescript-eslint/parser": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.1.tgz",
"integrity": "sha512-9Vb+KIv29r6GPu4EboWOnQM7T+UjpjXvjCPhNORlgm40a9Ia9bvaPJswvtae1gip2QEeVeGh6YquqAzEgoRAlw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"requires": {
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
"integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
"integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
"requires": {
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/utils": "5.47.1",
"@typescript-eslint/typescript-estree": "5.48.0",
"@typescript-eslint/utils": "5.48.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A=="
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw=="
},
"@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -12601,15 +12618,15 @@
}
},
"@typescript-eslint/utils": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
"integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
"integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@ -12626,11 +12643,11 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/types": "5.48.0",
"eslint-visitor-keys": "^3.3.0"
},
"dependencies": {
@ -13796,11 +13813,11 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint": {
"version": "8.30.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz",
"integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==",
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
"requires": {
"@eslint/eslintrc": "^1.4.0",
"@eslint/eslintrc": "^1.4.1",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@ -13976,9 +13993,9 @@
"requires": {}
},
"eslint-plugin-custom-elements": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.6.tgz",
"integrity": "sha512-JwPHRSOUe7y8dpC5hg90ySHejsfnQ3yqprv0902VMZ3j8FRZDudj+yzxqqkRDhZTNFUxP3r+0TWuveZhLgJONg==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.7.tgz",
"integrity": "sha512-s37mXujv/fAsPlvoLbO8jiZDM2gc/dFXIDf7wK52LMrsTsLpE5ITugQCwPM8lmcONjYdswS5kneX9LzJlt7aTA==",
"requires": {}
},
"eslint-plugin-lit": {
@ -15027,9 +15044,9 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE="
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ=="
},
"jsonfile": {
"version": "6.1.0",
@ -15988,9 +16005,9 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"pyright": {
"version": "1.1.285",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.285.tgz",
"integrity": "sha512-z+KJyaguJyVfEVxzVlyannjYtD13LGMdGTcQJuiq975f1nTHZ4oSwd9SrAZWP+rDHW/NVQCOz0CBlsMeNA/2sg=="
"version": "1.1.286",
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.286.tgz",
"integrity": "sha512-67+zViU+bN8m6cZQQc65aSinEguuFDI6gn2yf+naMjXuR5s65sAHAjP2FcPnd/AeB+F9fq4oytJ8UQOAbhLhFg=="
},
"qrjs": {
"version": "0.1.2",
@ -17025,6 +17042,11 @@
}
}
},
"turnstile-types": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/turnstile-types/-/turnstile-types-1.0.2.tgz",
"integrity": "sha512-Y98xYhxf9xtYuu1QCrQm7og0o2zw2bGdsyUNXeWgPVXsHFkJIwTrRY6o3Oioa9PjFtoYM+oHe1n23V+oE27WUQ=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -17374,9 +17396,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz",
"integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g=="
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
"integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw=="
},
"yargs": {
"version": "15.4.1",

View File

@ -12,6 +12,7 @@
"lit-analyse": "lit-analyzer src",
"prettier-check": "prettier --check .",
"prettier": "prettier --write .",
"tsc": "lingui compile && tsc --noEmit -p .",
"background-image": "npx @squoosh/cli -d src/assets/images --resize '{\"enabled\":true,\"width\":2560,\"method\":\"lanczos3\",\"fitMethod\":\"contain\",\"premultiply\":true,\"linearRGB\":true}' --mozjpeg '{\"quality\":75,\"baseline\":false,\"arithmetic\":false,\"progressive\":true,\"optimize_coding\":true,\"smoothing\":0,\"color_space\":3,\"quant_table\":3,\"trellis_multipass\":false,\"trellis_opt_zero\":false,\"trellis_opt_table\":false,\"trellis_loops\":1,\"auto_subsample\":true,\"chroma_subsample\":2,\"separate_chroma_quality\":false,\"chroma_quality\":75}' src/assets/images/flow_background.jpg"
},
"lingui": {
@ -64,7 +65,8 @@
"@codemirror/legacy-modes": "^6.3.1",
"@formatjs/intl-listformat": "^7.1.7",
"@fortawesome/fontawesome-free": "^6.2.1",
"@goauthentik/api": "^2022.11.4-1672221153",
"@goauthentik/api": "^2022.12.1-1672612412",
"@hcaptcha/types": "^1.0.3",
"@jackfranklin/rollup-plugin-markdown": "^0.4.0",
"@lingui/cli": "^3.15.0",
"@lingui/core": "^3.15.0",
@ -80,13 +82,13 @@
"@rollup/plugin-typescript": "^10.0.1",
"@sentry/browser": "^7.28.1",
"@sentry/tracing": "^7.28.1",
"@squoosh/cli": "^0.7.2",
"@squoosh/cli": "^0.7.3",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/chart.js": "^2.9.37",
"@types/codemirror": "5.60.5",
"@types/codemirror": "5.60.6",
"@types/grecaptcha": "^3.0.4",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@webcomponents/webcomponentsjs": "^2.7.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3",
@ -96,16 +98,16 @@
"codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0",
"country-flag-icons": "^1.5.5",
"eslint": "^8.30.0",
"eslint": "^8.31.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.6",
"eslint-plugin-custom-elements": "0.0.7",
"eslint-plugin-lit": "^1.7.2",
"fuse.js": "^6.6.2",
"lit": "^2.5.0",
"mermaid": "^9.3.0",
"moment": "^2.29.4",
"prettier": "^2.8.1",
"pyright": "^1.1.285",
"pyright": "^1.1.286",
"rapidoc": "^9.3.3",
"rollup": "^2.79.1",
"rollup-plugin-copy": "^3.4.0",
@ -114,8 +116,9 @@
"rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1",
"tslib": "^2.4.1",
"turnstile-types": "^1.0.2",
"typescript": "^4.9.4",
"webcomponent-qr-code": "^1.1.0",
"yaml": "^2.2.0"
"yaml": "^2.2.1"
}
}

View File

@ -24,6 +24,10 @@ export const resources = [
src: "node_modules/@patternfly/patternfly/patternfly-base.css",
dest: "dist/",
},
{
src: "node_modules/@patternfly/patternfly/components/Dropdown/dropdown.css",
dest: "dist/",
},
{
src: "node_modules/@patternfly/patternfly/components/Page/page.css",
dest: "dist/",

View File

@ -10,7 +10,7 @@ import { AdminApi, LoginMetrics } from "@goauthentik/api";
@customElement("ak-charts-admin-login-authorization")
export class AdminLoginAuthorizeChart extends AKChart<LoginMetrics> {
apiRequest(): Promise<LoginMetrics> {
async apiRequest(): Promise<LoginMetrics> {
return new AdminApi(DEFAULT_CONFIG).adminMetricsRetrieve();
}

View File

@ -2,17 +2,22 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { PFColor } from "@goauthentik/elements/Label";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { UserOption } from "@goauthentik/elements/user/utils";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { Application, CoreApi, PolicyTestResult } from "@goauthentik/api";
import {
Application,
CoreApi,
CoreUsersListRequest,
PolicyTestResult,
User,
} from "@goauthentik/api";
@customElement("ak-application-check-access-form")
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
@ -29,14 +34,13 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
return t`Successfully sent test-request.`;
}
send = (data: { forUser: number }): Promise<PolicyTestResult> => {
send = async (data: { forUser: number }): Promise<PolicyTestResult> => {
this.request = data.forUser;
return new CoreApi(DEFAULT_CONFIG)
.coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
})
.then((result) => (this.result = result));
const result = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({
slug: this.application?.slug,
forUser: data.forUser,
});
return (this.result = result);
};
resetForm(): void {
@ -114,25 +118,31 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="forUser">
<select class="pf-c-form-control">
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${user.pk.toString() === this.request?.toString()}
value=${user.pk}
>
${UserOption(user)}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",
};
if (query !== undefined) {
args.search = query;
}
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
return users.results;
}}
.renderElement=${(user: User): string => {
return user.username;
}}
.renderDescription=${(user: User): TemplateResult => {
return html`${user.name}`;
}}
.value=${(user: User | undefined): number | undefined => {
return user?.pk;
}}
.selected=${(user: User): boolean => {
return user.pk.toString() === this.request?.toString();
}}
>
</ak-search-select>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;

View File

@ -1,11 +1,13 @@
import "@goauthentik/admin/providers/ProviderWizard";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/ModalForm";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/ProxyForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -20,6 +22,7 @@ import {
CoreApi,
PolicyEngineMode,
Provider,
ProvidersAllListRequest,
ProvidersApi,
} from "@goauthentik/api";
@ -78,29 +81,6 @@ export class ApplicationForm extends ModelForm<Application, string> {
return app;
};
groupProviders(providers: Provider[]): TemplateResult {
const m = new Map<string, Provider[]>();
providers.forEach((p) => {
if (!m.has(p.verboseName || "")) {
m.set(p.verboseName || "", []);
}
const tProviders = m.get(p.verboseName || "") || [];
tProviders.push(p);
});
return html`
${Array.from(m).map(([group, providers]) => {
return html`<optgroup label=${group}>
${providers.map((p) => {
const selected = this.instance?.provider === p.pk || this.provider === p.pk;
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
${p.name}
</option>`;
})}
</optgroup>`;
})}
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
@ -132,17 +112,32 @@ export class ApplicationForm extends ModelForm<Application, string> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Provider`} name="provider">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.provider === undefined}>
---------
</option>
${until(
new ProvidersApi(DEFAULT_CONFIG).providersAllList({}).then((providers) => {
return this.groupProviders(providers.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Provider[]> => {
const args: ProvidersAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args);
return items.results;
}}
.renderElement=${(item: Provider): string => {
return item.name;
}}
.value=${(item: Provider | undefined): number | undefined => {
return item?.pk;
}}
.groupBy=${(items: Provider[]) => {
return groupBy(items, (item) => item.verboseName);
}}
.selected=${(item: Provider): boolean => {
return this.instance?.provider === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Select a provider that this application should use. Alternatively, create a new provider.`}
</p>
@ -159,20 +154,23 @@ export class ApplicationForm extends ModelForm<Application, string> {
?required=${true}
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to grant access.`}
</option>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to grant access.`}
</option>
</select>
<ak-radio
.options=${[
{
label: "ANY",
value: PolicyEngineMode.Any,
default: true,
description: html`${t`Any policy must match to grant access`}`,
},
{
label: "ALL",
value: PolicyEngineMode.All,
description: html`${t`All policies must match to grant access`}`,
},
]}
.value=${this.instance?.policyEngineMode}
>
</ak-radio>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`UI settings`} </span>

View File

@ -70,6 +70,9 @@ export class ApplicationListPage extends TablePage<Application> {
text-align: center;
vertical-align: middle;
}
.pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * {
margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2);
}
`,
);
}

View File

@ -1,6 +1,7 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import "@goauthentik/elements/wizard/WizardFormPage";
@ -8,13 +9,13 @@ import { t } from "@lingui/macro";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
ClientTypeEnum,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
OAuth2ProviderRequest,
ProvidersApi,
} from "@goauthentik/api";
@ -46,23 +47,29 @@ export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option value=${ifDefined(flow.pk)}>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow used when users access this application.`}
</p>

View File

@ -5,6 +5,7 @@ import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import YAML from "yaml";
import { t } from "@lingui/macro";
@ -12,12 +13,11 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
import { BlueprintInstance, ManagedApi } from "@goauthentik/api";
import { BlueprintFile, BlueprintInstance, ManagedApi } from "@goauthentik/api";
enum blueprintSource {
local,
@ -133,30 +133,36 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
<div class="pf-c-card__footer">
${this.source === blueprintSource.local
? html`<ak-form-element-horizontal label=${t`Path`} name="path">
<select class="pf-c-form-control">
${until(
new ManagedApi(DEFAULT_CONFIG)
.managedBlueprintsAvailableList()
.then((files) => {
return files.map((file) => {
let name = file.path;
if (file.meta && file.meta.name) {
name = `${name} (${file.meta.name})`;
}
const selected =
file.path === this.instance?.path;
return html`<option
?selected=${selected}
value=${file.path}
>
${name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select></ak-form-element-horizontal
>`
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<BlueprintFile[]> => {
const items = await new ManagedApi(
DEFAULT_CONFIG,
).managedBlueprintsAvailableList();
return items.filter((item) =>
query ? item.path.includes(query) : true,
);
}}
.renderElement=${(item: BlueprintFile): string => {
const name = item.path;
if (item.meta && item.meta.name) {
return `${name} (${item.meta.name})`;
}
return name;
}}
.value=${(
item: BlueprintFile | undefined,
): string | undefined => {
return item?.path;
}}
.selected=${(item: BlueprintFile): boolean => {
return this.instance?.path === item.path;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>`
: html``}
${this.source === blueprintSource.oci
? html`<ak-form-element-horizontal label=${t`URL`} name="path">

View File

@ -1,6 +1,8 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -9,7 +11,14 @@ import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { CoreApi, EventsApi, NotificationRule, SeverityEnum } from "@goauthentik/api";
import {
CoreApi,
CoreGroupsListRequest,
EventsApi,
Group,
NotificationRule,
SeverityEnum,
} from "@goauthentik/api";
@customElement("ak-event-rule-form")
export class RuleForm extends ModelForm<NotificationRule, string> {
@ -40,29 +49,6 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
}
};
renderSeverity(): TemplateResult {
return html`
<option
value=${SeverityEnum.Alert}
?selected=${this.instance?.severity === SeverityEnum.Alert}
>
${t`Alert`}
</option>
<option
value=${SeverityEnum.Warning}
?selected=${this.instance?.severity === SeverityEnum.Warning}
>
${t`Warning`}
</option>
<option
value=${SeverityEnum.Notice}
?selected=${this.instance?.severity === SeverityEnum.Notice}
>
${t`Notice`}
</option>
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
@ -74,30 +60,37 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Group`} name="group">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.group === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.group === group.pk}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(args);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group?.pk;
}}
.selected=${(group: Group): boolean => {
return group.pk === this.instance?.group;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Transports`} ?required=${true} name="transports">
<select name="users" class="pf-c-form-control" multiple>
<select class="pf-c-form-control" multiple>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsTransportsList({})
.eventsTransportsList({
ordering: "name",
})
.then((transports) => {
return transports.results.map((transport) => {
const selected = Array.from(
@ -124,9 +117,25 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Severity`} ?required=${true} name="severity">
<select class="pf-c-form-control">
${this.renderSeverity()}
</select>
<ak-radio
.options=${[
{
label: "Alert",
value: SeverityEnum.Alert,
default: true,
},
{
label: t`Warning`,
value: SeverityEnum.Warning,
},
{
label: t`Notice`,
value: SeverityEnum.Notice,
},
]}
.value=${this.instance?.severity}
>
</ak-radio>
</ak-form-element-horizontal>
</form>`;
}

View File

@ -2,19 +2,21 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
EventsApi,
NotificationTransport,
NotificationTransportModeEnum,
NotificationWebhookMapping,
PropertymappingsApi,
PropertymappingsNotificationListRequest,
} from "@goauthentik/api";
@customElement("ak-event-transport-form")
@ -132,26 +134,33 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
label=${t`Webhook Mapping`}
name="webhookMapping"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.webhookMapping === undefined}>
---------
</option>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsNotificationList({})
.then((mappings) => {
return mappings.results.map((mapping) => {
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${this.instance?.webhookMapping === mapping.pk}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<NotificationWebhookMapping[]> => {
const args: PropertymappingsNotificationListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsNotificationList(args);
return items.results;
}}
.renderElement=${(item: NotificationWebhookMapping): string => {
return item.name;
}}
.value=${(item: NotificationWebhookMapping | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: NotificationWebhookMapping): boolean => {
return this.instance?.webhookMapping === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="sendOnce">
<div class="pf-c-check">

View File

@ -2,8 +2,10 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import { t } from "@lingui/macro";
@ -74,140 +76,6 @@ export class FlowForm extends ModelForm<Flow, string> {
return flow;
};
renderDesignations(): TemplateResult {
return html`
<option
value=${FlowDesignationEnum.Authentication}
?selected=${this.instance?.designation === FlowDesignationEnum.Authentication}
>
${DesignationToLabel(FlowDesignationEnum.Authentication)}
</option>
<option
value=${FlowDesignationEnum.Authorization}
?selected=${this.instance?.designation === FlowDesignationEnum.Authorization}
>
${DesignationToLabel(FlowDesignationEnum.Authorization)}
</option>
<option
value=${FlowDesignationEnum.Enrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Enrollment}
>
${DesignationToLabel(FlowDesignationEnum.Enrollment)}
</option>
<option
value=${FlowDesignationEnum.Invalidation}
?selected=${this.instance?.designation === FlowDesignationEnum.Invalidation}
>
${DesignationToLabel(FlowDesignationEnum.Invalidation)}
</option>
<option
value=${FlowDesignationEnum.Recovery}
?selected=${this.instance?.designation === FlowDesignationEnum.Recovery}
>
${DesignationToLabel(FlowDesignationEnum.Recovery)}
</option>
<option
value=${FlowDesignationEnum.StageConfiguration}
?selected=${this.instance?.designation === FlowDesignationEnum.StageConfiguration}
>
${DesignationToLabel(FlowDesignationEnum.StageConfiguration)}
</option>
<option
value=${FlowDesignationEnum.Unenrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Unenrollment}
>
${DesignationToLabel(FlowDesignationEnum.Unenrollment)}
</option>
`;
}
renderDeniedAction(): TemplateResult {
return html` <option
value=${DeniedActionEnum.MessageContinue}
?selected=${this.instance?.deniedAction === DeniedActionEnum.MessageContinue}
>
${t`MESSAGE_CONTINUE will follow the ?next parameter if set, otherwise show a message.`}
</option>
<option
value=${DeniedActionEnum.Continue}
?selected=${this.instance?.deniedAction === DeniedActionEnum.Continue}
>
${t`CONTINUE will either follow the ?next parameter or redirect to the default interface.`}
</option>
<option
value=${DeniedActionEnum.Message}
?selected=${this.instance?.deniedAction === DeniedActionEnum.Message}
>
${t`MESSAGE will notify the user the flow isn't applicable.`}
</option>`;
}
renderAuthentication(): TemplateResult {
return html`
<option
value=${AuthenticationEnum.None}
?selected=${this.instance?.authentication === AuthenticationEnum.None}
>
${t`No requirement`}
</option>
<option
value=${AuthenticationEnum.RequireAuthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireAuthenticated}
>
${t`Require authentication`}
</option>
<option
value=${AuthenticationEnum.RequireUnauthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireUnauthenticated}
>
${t`Require no authentication.`}
</option>
<option
value=${AuthenticationEnum.RequireSuperuser}
?selected=${this.instance?.authentication === AuthenticationEnum.RequireSuperuser}
>
${t`Require superuser.`}
</option>
`;
}
renderLayout(): TemplateResult {
return html`
<option
value=${LayoutEnum.Stacked}
?selected=${this.instance?.layout === LayoutEnum.Stacked}
>
${LayoutToLabel(LayoutEnum.Stacked)}
</option>
<option
value=${LayoutEnum.ContentLeft}
?selected=${this.instance?.layout === LayoutEnum.ContentLeft}
>
${LayoutToLabel(LayoutEnum.ContentLeft)}
</option>
<option
value=${LayoutEnum.ContentRight}
?selected=${this.instance?.layout === LayoutEnum.ContentRight}
>
${LayoutToLabel(LayoutEnum.ContentRight)}
</option>
<option
value=${LayoutEnum.SidebarLeft}
?selected=${this.instance?.layout === LayoutEnum.SidebarLeft}
>
${LayoutToLabel(LayoutEnum.SidebarLeft)}
</option>
<option
value=${LayoutEnum.SidebarRight}
?selected=${this.instance?.layout === LayoutEnum.SidebarRight}
>
${LayoutToLabel(LayoutEnum.SidebarRight)}
</option>
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
@ -236,38 +104,6 @@ export class FlowForm extends ModelForm<Flow, string> {
/>
<p class="pf-c-form__helper-text">${t`Visible in the URL.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Policy engine mode`}
?required=${true}
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to grant access.`}
</option>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to grant access.`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authentication`}
?required=${true}
name="authentication"
>
<select class="pf-c-form-control">
${this.renderAuthentication()}
</select>
<p class="pf-c-form__helper-text">
${t`Required authentication level for this flow.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Designation`}
?required=${true}
@ -277,99 +113,264 @@ export class FlowForm extends ModelForm<Flow, string> {
<option value="" ?selected=${this.instance?.designation === undefined}>
---------
</option>
${this.renderDesignations()}
<option
value=${FlowDesignationEnum.Authentication}
?selected=${this.instance?.designation ===
FlowDesignationEnum.Authentication}
>
${DesignationToLabel(FlowDesignationEnum.Authentication)}
</option>
<option
value=${FlowDesignationEnum.Authorization}
?selected=${this.instance?.designation ===
FlowDesignationEnum.Authorization}
>
${DesignationToLabel(FlowDesignationEnum.Authorization)}
</option>
<option
value=${FlowDesignationEnum.Enrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Enrollment}
>
${DesignationToLabel(FlowDesignationEnum.Enrollment)}
</option>
<option
value=${FlowDesignationEnum.Invalidation}
?selected=${this.instance?.designation === FlowDesignationEnum.Invalidation}
>
${DesignationToLabel(FlowDesignationEnum.Invalidation)}
</option>
<option
value=${FlowDesignationEnum.Recovery}
?selected=${this.instance?.designation === FlowDesignationEnum.Recovery}
>
${DesignationToLabel(FlowDesignationEnum.Recovery)}
</option>
<option
value=${FlowDesignationEnum.StageConfiguration}
?selected=${this.instance?.designation ===
FlowDesignationEnum.StageConfiguration}
>
${DesignationToLabel(FlowDesignationEnum.StageConfiguration)}
</option>
<option
value=${FlowDesignationEnum.Unenrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Unenrollment}
>
${DesignationToLabel(FlowDesignationEnum.Unenrollment)}
</option>
</select>
<p class="pf-c-form__helper-text">
${t`Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Denied action`}
label=${t`Authentication`}
?required=${true}
name="deniedAction"
name="authentication"
>
<select class="pf-c-form-control">
${this.renderDeniedAction()}
<option
value=${AuthenticationEnum.None}
?selected=${this.instance?.authentication === AuthenticationEnum.None}
>
${t`No requirement`}
</option>
<option
value=${AuthenticationEnum.RequireAuthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireAuthenticated}
>
${t`Require authentication`}
</option>
<option
value=${AuthenticationEnum.RequireUnauthenticated}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireUnauthenticated}
>
${t`Require no authentication.`}
</option>
<option
value=${AuthenticationEnum.RequireSuperuser}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireSuperuser}
>
${t`Require superuser.`}
</option>
</select>
<p class="pf-c-form__helper-text">
${t`Decides the response when a policy denies access to this flow for a user.`}
${t`Required authentication level for this flow.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Layout`} ?required=${true} name="layout">
<select class="pf-c-form-control">
${this.renderLayout()}
</select>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
<ak-form-group>
<span slot="header"> ${t`Behavior settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="compatibilityMode">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.compatibilityMode, false)}
/>
<label class="pf-c-check__label"> ${t`Compatibility mode`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`Increases compatibility with password managers and mobile devices.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Denied action`}
?required=${true}
name="deniedAction"
>
<ak-radio
.options=${[
{
label: "MESSAGE_CONTINUE",
value: DeniedActionEnum.MessageContinue,
default: true,
description: html`${t`Will follow the ?next parameter if set, otherwise show a message`}`,
},
{
label: "CONTINUE",
value: DeniedActionEnum.Continue,
description: html`${t`Will either follow the ?next parameter or redirect to the default interface`}`,
},
{
label: "MESSAGE",
value: DeniedActionEnum.Message,
description: html`${t`Will notify the user the flow isn't applicable`}`,
},
]}
.value=${this.instance?.deniedAction}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Decides the response when a policy denies access to this flow for a user.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Policy engine mode`}
?required=${true}
name="policyEngineMode"
>
<ak-radio
.options=${[
{
label: "ANY",
value: PolicyEngineMode.Any,
default: true,
description: html`${t`Any policy must match to grant access`}`,
},
{
label: "ALL",
value: PolicyEngineMode.All,
description: html`${t`All policies must match to grant access`}`,
},
]}
.value=${this.instance?.policyEngineMode}
>
</ak-radio>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${t`Appearance settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Layout`} ?required=${true} name="layout">
<select class="pf-c-form-control">
<option
value=${LayoutEnum.Stacked}
?selected=${this.instance?.layout === LayoutEnum.Stacked}
>
${LayoutToLabel(LayoutEnum.Stacked)}
</option>
<option
value=${LayoutEnum.ContentLeft}
?selected=${this.instance?.layout === LayoutEnum.ContentLeft}
>
${LayoutToLabel(LayoutEnum.ContentLeft)}
</option>
<option
value=${LayoutEnum.ContentRight}
?selected=${this.instance?.layout === LayoutEnum.ContentRight}
>
${LayoutToLabel(LayoutEnum.ContentRight)}
</option>
<option
value=${LayoutEnum.SidebarLeft}
?selected=${this.instance?.layout === LayoutEnum.SidebarLeft}
>
${LayoutToLabel(LayoutEnum.SidebarLeft)}
</option>
<option
value=${LayoutEnum.SidebarRight}
?selected=${this.instance?.layout === LayoutEnum.SidebarRight}
>
${LayoutToLabel(LayoutEnum.SidebarRight)}
</option>
</select>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.background
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`}
${this.instance?.background}
</p>
`
: html``}
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>
${this.instance?.background
? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}
/>
<label class="pf-c-check__label">
${t`Clear background image`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`Delete currently set background image.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.background
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.background}
</p>
`
: html``}
<input
type="text"
value="${first(this.instance?.background, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>
${this.instance?.background
? html`
<ak-form-element-horizontal>
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}
/>
<label class="pf-c-check__label">
${t`Clear background image`}
</label>
</div>
<p class="pf-c-form__helper-text">
${t`Delete currently set background image.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input
type="text"
value="${first(this.instance?.background, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>`;
}),
)}
<ak-form-element-horizontal name="compatibilityMode">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.compatibilityMode, false)}
/>
<label class="pf-c-check__label"> ${t`Compatibility mode`} </label>
</ak-form-element-horizontal>`;
}),
)}
</div>
<p class="pf-c-form__helper-text">
${t`Enable compatibility mode, increases compatibility with password managers on mobile devices.`}
</p>
</ak-form-element-horizontal>
</ak-form-group>
</form>`;
}
}

View File

@ -26,23 +26,19 @@ export class FlowImportForm extends Form<Flow> {
return super.styles.concat(PFDescriptionList);
}
// eslint-disable-next-line
send = (data: Flow): Promise<FlowImportResult> => {
send = async (): Promise<FlowImportResult> => {
const file = this.getFormFiles()["flow"];
if (!file) {
throw new SentryIgnoredError("No form data");
}
return new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesImportCreate({
file: file,
})
.then((result) => {
if (!result.success) {
this.result = result;
throw new SentryIgnoredError("Failed to import flow");
}
return result;
});
const result = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesImportCreate({
file: file,
});
if (!result.success) {
this.result = result;
throw new SentryIgnoredError("Failed to import flow");
}
return result;
};
renderResult(): TemplateResult {

View File

@ -2,6 +2,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -16,6 +18,7 @@ import {
InvalidResponseActionEnum,
PolicyEngineMode,
Stage,
StagesAllListRequest,
StagesApi,
} from "@goauthentik/api";
@ -51,22 +54,6 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
}
};
groupStages(stages: Stage[]): TemplateResult {
return html`
<option value="">---------</option>
${groupBy<Stage>(stages, (s) => s.verboseName || "").map(([group, stages]) => {
return html`<optgroup label=${group}>
${stages.map((stage) => {
const selected = this.instance?.stage === stage.pk;
return html`<option ?selected=${selected} value=${ifDefined(stage.pk)}>
${stage.name}
</option>`;
})}
</optgroup>`;
})}
`;
}
async getOrder(): Promise<number> {
if (this.instance?.pk) {
return this.instance.order;
@ -117,18 +104,31 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
return html`<form class="pf-c-form pf-m-horizontal">
${this.renderTarget()}
<ak-form-element-horizontal label=${t`Stage`} ?required=${true} name="stage">
<select class="pf-c-form-control">
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesAllList({
ordering: "name",
})
.then((stages) => {
return this.groupStages(stages.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
const args: StagesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const stages = await new StagesApi(DEFAULT_CONFIG).stagesAllList(args);
return stages.results;
}}
.groupBy=${(items: Stage[]) => {
return groupBy(items, (stage) => stage.verboseNamePlural);
}}
.renderElement=${(stage: Stage): string => {
return stage.name;
}}
.value=${(stage: Stage | undefined): string | undefined => {
return stage?.pk;
}}
.selected=${(stage: Stage): boolean => {
return stage.pk === this.instance?.stage;
}}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
<!-- @ts-ignore -->
@ -166,35 +166,34 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Invalid response action`}
label=${t`Invalid response behavior`}
?required=${true}
name="invalidResponseAction"
>
<select class="pf-c-form-control">
<option
value=${InvalidResponseActionEnum.Retry}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.Retry}
>
${t`RETRY returns the error message and a similar challenge to the executor.`}
</option>
<option
value=${InvalidResponseActionEnum.Restart}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.Restart}
>
${t`RESTART restarts the flow from the beginning.`}
</option>
<option
value=${InvalidResponseActionEnum.RestartWithContext}
?selected=${this.instance?.invalidResponseAction ===
InvalidResponseActionEnum.RestartWithContext}
>
${t`RESTART_WITH_CONTEXT restarts the flow from the beginning, while keeping the flow context.`}
</option>
</select>
<ak-radio
.options=${[
{
label: "RETRY",
value: InvalidResponseActionEnum.Retry,
default: true,
description: html`${t`Returns the error message and a similar challenge to the executor`}`,
},
{
label: "RESTART",
value: InvalidResponseActionEnum.Restart,
description: html`${t`Restarts the flow from the beginning`}`,
},
{
label: "RESTART_WITH_CONTEXT",
value: InvalidResponseActionEnum.RestartWithContext,
description: html`${t`Restarts the flow from the beginning, while keeping the flow context`}`,
},
]}
.value=${this.instance?.invalidResponseAction}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Configure how the flow executor should handle an invalid response to a challenge.`}
${t`Configure how the flow executor should handle an invalid response to a challenge given by this bound stage.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
@ -202,20 +201,23 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
?required=${true}
name="policyEngineMode"
>
<select class="pf-c-form-control">
<option
value=${PolicyEngineMode.Any}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}
>
${t`ANY, any policy must match to include this stage access.`}
</option>
<option
value=${PolicyEngineMode.All}
?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}
>
${t`ALL, all policies must match to include this stage access.`}
</option>
</select>
<ak-radio
.options=${[
{
label: "ANY",
value: PolicyEngineMode.Any,
default: true,
description: html`${t`Any policy must match to grant access`}`,
},
{
label: "ALL",
value: PolicyEngineMode.All,
description: html`${t`All policies must match to grant access`}`,
},
]}
.value=${this.instance?.policyEngineMode}
>
</ak-radio>
</ak-form-element-horizontal>
</form>`;
}

View File

@ -18,6 +18,8 @@ export function DesignationToLabel(designation: FlowDesignationEnum): string {
return t`Stage Configuration`;
case FlowDesignationEnum.Unenrollment:
return t`Unenrollment`;
case FlowDesignationEnum.UnknownDefaultOpenApi:
return t`Unknown designation`;
}
}
@ -33,5 +35,7 @@ export function LayoutToLabel(layout: LayoutEnum): string {
return t`Sidebar left`;
case LayoutEnum.SidebarRight:
return t`Sidebar right`;
case LayoutEnum.UnknownDefaultOpenApi:
return t`Unknown layout`;
}
}

View File

@ -6,6 +6,7 @@ import "@goauthentik/elements/chips/Chip";
import "@goauthentik/elements/chips/ChipGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { UserOption } from "@goauthentik/elements/user/utils";
import YAML from "yaml";
@ -16,7 +17,7 @@ import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { CoreApi, Group, User } from "@goauthentik/api";
import { CoreApi, CoreGroupsListRequest, Group, User } from "@goauthentik/api";
@customElement("ak-group-form")
export class GroupForm extends ModelForm<Group, string> {
@ -83,24 +84,29 @@ export class GroupForm extends ModelForm<Group, string> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Parent`} name="parent">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.parent === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.parent === group.pk}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(args);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group?.pk;
}}
.selected=${(group: Group): boolean => {
return group.pk === this.instance?.parent;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Members`} name="users">
<div class="pf-c-input-group">

View File

@ -32,8 +32,8 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
return t`Successfully added user to group(s).`;
}
send = (data: { groups: string[] }): Promise<{ groups: string[] }> => {
return Promise.all(
send = async (data: { groups: string[] }): Promise<{ groups: string[] }> => {
await Promise.all(
data.groups.map((group) => {
return new CoreApi(DEFAULT_CONFIG).coreGroupsAddUserCreate({
groupUuid: group,
@ -42,9 +42,8 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
},
});
}),
).then(() => {
return data;
});
);
return data;
};
renderForm(): TemplateResult {

View File

@ -1,8 +1,10 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { docLink } from "@goauthentik/common/global";
import { groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import YAML from "yaml";
import { t } from "@lingui/macro";
@ -12,7 +14,14 @@ import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { Outpost, OutpostTypeEnum, OutpostsApi, ProvidersApi } from "@goauthentik/api";
import {
Outpost,
OutpostTypeEnum,
OutpostsApi,
OutpostsServiceConnectionsAllListRequest,
ProvidersApi,
ServiceConnection,
} from "@goauthentik/api";
@customElement("ak-outpost-form")
export class OutpostForm extends ModelForm<Outpost, string> {
@ -98,6 +107,10 @@ export class OutpostForm extends ModelForm<Outpost, string> {
</option>`;
});
});
case OutpostTypeEnum.UnknownDefaultOpenApi:
return Promise.resolve([
html` <option value="">${t`Unknown outpost type`}</option>`,
]);
}
}
@ -134,32 +147,38 @@ export class OutpostForm extends ModelForm<Outpost, string> {
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Integration`} name="serviceConnection">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.serviceConnection === undefined}>
---------
</option>
${until(
new OutpostsApi(DEFAULT_CONFIG)
.outpostsServiceConnectionsAllList({
ordering: "name",
})
.then((scs) => {
return scs.results.map((sc) => {
let selected = this.instance?.serviceConnection === sc.pk;
if (scs.results.length === 1 && !this.instance) {
selected = true;
}
return html`<option
value=${ifDefined(sc.pk)}
?selected=${selected}
>
${sc.name} (${sc.verboseName})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<ServiceConnection[]> => {
const args: OutpostsServiceConnectionsAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new OutpostsApi(
DEFAULT_CONFIG,
).outpostsServiceConnectionsAllList(args);
return items.results;
}}
.renderElement=${(item: ServiceConnection): string => {
return item.name;
}}
.value=${(item: ServiceConnection | undefined): string | undefined => {
return item?.pk;
}}
.groupBy=${(items: ServiceConnection[]) => {
return groupBy(items, (item) => item.verboseName);
}}
.selected=${(item: ServiceConnection, items: ServiceConnection[]): boolean => {
let selected = this.instance?.serviceConnection === item.pk;
if (items.length === 1 && !this.instance) {
selected = true;
}
return selected;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Selecting an integration enables the management of the outpost by authentik.`}
</p>

View File

@ -8,6 +8,7 @@ import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import AKGlobal from "@goauthentik/common/styles/authentik.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { OutpostHealth } from "@goauthentik/api";
@ -20,6 +21,7 @@ export class OutpostHealthElement extends AKElement {
static get styles(): CSSResult[] {
return [
PFBase,
PFDescriptionList,
AKGlobal,
css`
li {
@ -40,21 +42,43 @@ export class OutpostHealthElement extends AKElement {
8,
)})`;
}
return html` <ul>
<li>
<ak-label color=${PFColor.Green}>
${t`Last seen: ${this.outpostHealth.lastSeen?.toLocaleTimeString()}`}
</ak-label>
</li>
<li>
${this.outpostHealth.versionOutdated
? html`<ak-label color=${PFColor.Red}
>${t`${this.outpostHealth.version}, should be ${this.outpostHealth.versionShould}`}
</ak-label>`
: html`<ak-label color=${PFColor.Green}
>${t`Version: ${versionString}`}
</ak-label>`}
</li>
</ul>`;
return html`<dl class="pf-c-description-list pf-m-compact">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Last seen`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-label color=${PFColor.Green} ?compact=${true}>
${this.outpostHealth.lastSeen?.toLocaleTimeString()}
</ak-label>
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Version`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.outpostHealth.versionOutdated
? html`<ak-label color=${PFColor.Red} ?compact=${true}
>${t`${this.outpostHealth.version}, should be ${this.outpostHealth.versionShould}`}
</ak-label>`
: html`<ak-label color=${PFColor.Green} ?compact=${true}
>${versionString}
</ak-label>`}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Hostname`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">${this.outpostHealth.hostname}</div>
</dd>
</div>
</dl> `;
}
}

View File

@ -33,6 +33,8 @@ export function TypeToLabel(type?: OutpostTypeEnum): string {
return t`Proxy`;
case OutpostTypeEnum.Ldap:
return t`LDAP`;
case OutpostTypeEnum.UnknownDefaultOpenApi:
return t`Unknown type`;
}
}
@ -129,7 +131,7 @@ export class OutpostListPage extends TablePage<Outpost> {
return html`<td role="cell" colspan="5">
<div class="pf-c-table__expandable-row-content">
<h3>
${t`Detailed health (one instance per column, data is cached so may be out of data)`}
${t`Detailed health (one instance per column, data is cached so may be out of date)`}
</h3>
<dl class="pf-c-description-list pf-m-3-col-on-lg">
${until(

View File

@ -2,15 +2,21 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { CryptoApi, DockerServiceConnection, OutpostsApi } from "@goauthentik/api";
import {
CertificateKeyPair,
CryptoApi,
CryptoCertificatekeypairsListRequest,
DockerServiceConnection,
OutpostsApi,
} from "@goauthentik/api";
@customElement("ak-service-connection-docker-form")
export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnection, string> {
@ -79,34 +85,33 @@ export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnecti
label=${t`TLS Verification Certificate`}
name="tlsVerification"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.tlsVerification === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
includeDetails: false,
})
.then((certs) => {
return certs.results.map((cert) => {
return html`<option
value=${ifDefined(cert.pk)}
?selected=${this.instance?.tlsVerification === cert.pk}
>
${cert.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.tlsVerification || undefined)}
?selected=${this.instance?.tlsVerification !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return this.instance?.tlsVerification === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`CA which the endpoint's Certificate is verified against. Can be left empty for no validation.`}
</p>
@ -115,34 +120,33 @@ export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnecti
label=${t`TLS Authentication Certificate/SSH Keypair`}
name="tlsAuthentication"
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.tlsAuthentication === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
includeDetails: false,
})
.then((certs) => {
return certs.results.map((cert) => {
return html`<option
value=${ifDefined(cert.pk)}
?selected=${this.instance?.tlsAuthentication === cert.pk}
>
${cert.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.tlsAuthentication || undefined)}
?selected=${this.instance?.tlsAuthentication !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return this.instance?.tlsAuthentication === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Certificate/Key used for authentication. Can be left empty for no authentication.`}
</p>

View File

@ -1,9 +1,8 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/SearchSelect";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { UserOption } from "@goauthentik/elements/user/utils";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -21,6 +20,7 @@ import {
CoreGroupsListRequest,
CoreUsersListRequest,
Group,
PoliciesAllListRequest,
PoliciesApi,
Policy,
PolicyBinding,
@ -96,21 +96,6 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
}
};
groupPolicies(policies: Policy[]): TemplateResult {
return html`
${groupBy<Policy>(policies, (p) => p.verboseName || "").map(([group, policies]) => {
return html`<optgroup label=${group}>
${policies.map((p) => {
const selected = this.instance?.policy === p.pk;
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
${p.name}
</option>`;
})}
</optgroup>`;
})}
`;
}
async getOrder(): Promise<number> {
if (this.instance?.pk) {
return this.instance.order;
@ -181,28 +166,40 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
name="policy"
?hidden=${this.policyGroupUser !== target.policy}
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.policy === undefined}>
---------
</option>
${until(
new PoliciesApi(DEFAULT_CONFIG)
.policiesAllList({
ordering: "name",
})
.then((policies) => {
return this.groupPolicies(policies.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.groupBy=${(items: Policy[]) => {
return groupBy(items, (policy) => policy.verboseNamePlural);
}}
.fetchObjects=${async (query?: string): Promise<Policy[]> => {
const args: PoliciesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const policies = await new PoliciesApi(
DEFAULT_CONFIG,
).policiesAllList(args);
return policies.results;
}}
.renderElement=${(policy: Policy): string => {
return policy.name;
}}
.value=${(policy: Policy | undefined): string | undefined => {
return policy?.pk;
}}
.selected=${(policy: Policy): boolean => {
return policy.pk === this.instance?.policy;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group`}
name="group"
?hidden=${this.policyGroupUser !== target.group}
>
<!-- @ts-ignore -->
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
@ -220,7 +217,7 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group ? group.pk : undefined;
return group?.pk;
}}
.selected=${(group: Group): boolean => {
return group.pk === this.instance?.group;
@ -239,7 +236,6 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
name="user"
?hidden=${this.policyGroupUser !== target.user}
>
<!-- @ts-ignore -->
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
@ -252,10 +248,13 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
return users.results;
}}
.renderElement=${(user: User): string => {
return UserOption(user);
return user.username;
}}
.renderDescription=${(user: User): TemplateResult => {
return html`${user.name}`;
}}
.value=${(user: User | undefined): number | undefined => {
return user ? user.pk : undefined;
return user?.pk;
}}
.selected=${(user: User): boolean => {
return user.pk === this.instance?.user;

View File

@ -4,23 +4,24 @@ import "@goauthentik/elements/CodeMirror";
import { PFColor } from "@goauthentik/elements/Label";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { UserOption } from "@goauthentik/elements/user/utils";
import "@goauthentik/elements/forms/SearchSelect";
import YAML from "yaml";
import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import {
CoreApi,
CoreUsersListRequest,
PoliciesApi,
Policy,
PolicyTestRequest,
PolicyTestResult,
User,
} from "@goauthentik/api";
@customElement("ak-policy-test-form")
@ -38,14 +39,13 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
return t`Successfully sent test-request.`;
}
send = (data: PolicyTestRequest): Promise<PolicyTestResult> => {
send = async (data: PolicyTestRequest): Promise<PolicyTestResult> => {
this.request = data;
return new PoliciesApi(DEFAULT_CONFIG)
.policiesAllTestCreate({
policyUuid: this.policy?.pk || "",
policyTestRequest: data,
})
.then((result) => (this.result = result));
const result = await new PoliciesApi(DEFAULT_CONFIG).policiesAllTestCreate({
policyUuid: this.policy?.pk || "",
policyTestRequest: data,
});
return (this.result = result);
};
static get styles(): CSSResult[] {
@ -119,26 +119,31 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
<select class="pf-c-form-control">
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${this.request?.user.toString() ===
user.pk.toString()}
value=${user.pk}
>
${UserOption(user)}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",
};
if (query !== undefined) {
args.search = query;
}
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
return users.results;
}}
.renderElement=${(user: User): string => {
return user.username;
}}
.renderDescription=${(user: User): TemplateResult => {
return html`${user.name}`;
}}
.value=${(user: User | undefined): number | undefined => {
return user?.pk;
}}
.selected=${(user: User): boolean => {
return this.request?.user.toString() === user.pk.toString();
}}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror

View File

@ -3,6 +3,7 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -11,7 +12,7 @@ import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { AdminApi, EventMatcherPolicy, EventsApi, PoliciesApi } from "@goauthentik/api";
import { AdminApi, EventMatcherPolicy, EventsApi, PoliciesApi, TypeCreate } from "@goauthentik/api";
@customElement("ak-policy-event-matcher-form")
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
@ -72,27 +73,27 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Action`} name="action">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.action === undefined}>
---------
</option>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsEventsActionsList()
.then((actions) => {
return actions.map((action) => {
return html`<option
value=${action.component}
?selected=${this.instance?.action ===
action.component}
>
${action.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {
const items = await new EventsApi(
DEFAULT_CONFIG,
).eventsEventsActionsList();
return items.filter((item) =>
query ? item.name.includes(query) : true,
);
}}
.renderElement=${(item: TypeCreate): string => {
return item.name;
}}
.value=${(item: TypeCreate | undefined): string | undefined => {
return item?.component;
}}
.selected=${(item: TypeCreate): boolean => {
return this.instance?.action === item.component;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Match created events with this action type. When left empty, all action types will be matched.`}
</p>

View File

@ -3,7 +3,7 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { UserOption } from "@goauthentik/elements/user/utils";
import "@goauthentik/elements/forms/SearchSelect";
import YAML from "yaml";
import { t } from "@lingui/macro";
@ -11,14 +11,15 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CoreApi,
CoreUsersListRequest,
PolicyTestRequest,
PropertyMapping,
PropertyMappingTestResult,
PropertymappingsApi,
User,
} from "@goauthentik/api";
@customElement("ak-property-mapping-test-form")
@ -36,15 +37,14 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
return t`Successfully sent test-request.`;
}
send = (data: PolicyTestRequest): Promise<PropertyMappingTestResult> => {
send = async (data: PolicyTestRequest): Promise<PropertyMappingTestResult> => {
this.request = data;
return new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsAllTestCreate({
pmUuid: this.mapping?.pk || "",
policyTestRequest: data,
formatResult: true,
})
.then((result) => (this.result = result));
const result = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTestCreate({
pmUuid: this.mapping?.pk || "",
policyTestRequest: data,
formatResult: true,
});
return (this.result = result);
};
renderResult(): TemplateResult {
@ -67,26 +67,31 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
<select class="pf-c-form-control">
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${this.request?.user.toString() ===
user.pk.toString()}
value=${user.pk}
>
${UserOption(user)}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",
};
if (query !== undefined) {
args.search = query;
}
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
return users.results;
}}
.renderElement=${(user: User): string => {
return user.username;
}}
.renderDescription=${(user: User): TemplateResult => {
return html`${user.name}`;
}}
.value=${(user: User | undefined): number | undefined => {
return user?.pk;
}}
.selected=${(user: User): boolean => {
return this.request?.user.toString() === user.pk.toString();
}}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}

View File

@ -3,6 +3,8 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -12,10 +14,16 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CertificateKeyPair,
CoreApi,
CoreGroupsListRequest,
CryptoApi,
CryptoCertificatekeypairsListRequest,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
Group,
LDAPAPIAccessMode,
LDAPProvider,
ProvidersApi,
@ -66,98 +74,122 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
tenant().then((t) => {
return new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = flow.pk === t.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
${until(
tenant().then((t) => {
return html`
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation:
FlowsInstancesListDesignationEnum.Authentication,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(
DEFAULT_CONFIG,
).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.name;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.slug}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
let selected = flow.pk === t.flowAuthentication;
if (this.instance?.authorizationFlow === flow.pk) {
selected = true;
}
return selected;
}}
>
</ak-search-select>
`;
}),
html`<option>${t`Loading...`}</option>`,
)}
<p class="pf-c-form__helper-text">
${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Search group`} name="searchGroup">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.searchGroup === undefined}>
---------
</option>
${until(
new CoreApi(DEFAULT_CONFIG).coreGroupsList({}).then((groups) => {
return groups.results.map((group) => {
return html`<option
value=${ifDefined(group.pk)}
?selected=${this.instance?.searchGroup === group.pk}
>
${group.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(args);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group?.pk;
}}
.selected=${(group: Group): boolean => {
return group.pk === this.instance?.searchGroup;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Bind mode`} name="bindMode">
<select class="pf-c-form-control">
<option
value="${LDAPAPIAccessMode.Cached}"
?selected=${this.instance?.bindMode === LDAPAPIAccessMode.Cached}
>
${t`Cached binding, flow is executed and session is cached in memory. Flow is executed when session expires.`}
</option>
<option
value="${LDAPAPIAccessMode.Direct}"
?selected=${this.instance?.bindMode === LDAPAPIAccessMode.Direct}
>
${t`Direct binding, always execute the configured bind flow to authenticate the user.`}
</option>
</select>
<ak-radio
.options=${[
{
label: t`Cached binding`,
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${t`Flow is executed and session is cached in memory. Flow is executed when session expires`}`,
},
{
label: t`Direct binding`,
value: LDAPAPIAccessMode.Direct,
description: html`${t`Always execute the configured bind flow to authenticate the user`}`,
},
]}
.value=${this.instance?.bindMode}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Configure how the outpost authenticates requests.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Search mode`} name="searchMode">
<select class="pf-c-form-control">
<option
value="${LDAPAPIAccessMode.Cached}"
?selected=${this.instance?.searchMode === LDAPAPIAccessMode.Cached}
>
${t`Cached querying, the outpost holds all users and groups in-memory and will refresh every 5 Minutes.`}
</option>
<option
value="${LDAPAPIAccessMode.Direct}"
?selected=${this.instance?.searchMode === LDAPAPIAccessMode.Direct}
>
${t`Direct querying, always returns the latest data, but slower than cached querying.`}
</option>
</select>
<ak-radio
.options=${[
{
label: t`Cached querying`,
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${t`The outpost holds all users and groups in-memory and will refresh every 5 Minutes`}`,
},
{
label: t`Direct querying`,
value: LDAPAPIAccessMode.Direct,
description: html`${t`Always returns the latest data, but slower than cached querying`}`,
},
]}
.value=${this.instance?.searchMode}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Configure how the outpost queries the core authentik server's users.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">
@ -173,37 +205,37 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.certificate === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
hasKey: true,
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.certificate === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.certificate || undefined)}
?selected=${this.instance?.certificate !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return item.pk === this.instance?.certificate;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Due to protocol limitations, this certificate is only used when the outpost has a single provider.`}
${t`Due to protocol limitations, this certificate is only used when the outpost has a single provider, or all providers use the same certificate.`}
</p>
<p class="pf-c-form__helper-text">
${t`If multiple providers share an outpost, a self-signed certificate is used.`}

View File

@ -3,6 +3,8 @@ import { first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { t } from "@lingui/macro";
@ -13,10 +15,14 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CertificateKeyPair,
ClientTypeEnum,
CryptoApi,
CryptoCertificatekeypairsListRequest,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
IssuerModeEnum,
OAuth2Provider,
PropertymappingsApi,
@ -77,26 +83,32 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
return flow.pk === this.instance?.authorizationFlow;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
@ -180,39 +192,42 @@ ${this.instance?.redirectUris}</textarea
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Signing Key`} name="signingKey">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.signingKey === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
hasKey: true,
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
let selected = this.instance?.signingKey === key.pk;
if (!this.instance && keys.results.length === 1) {
selected = true;
}
return html`<option
value=${ifDefined(key.pk)}
?selected=${selected}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.signingKey || undefined)}
?selected=${this.instance?.signingKey !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(
item: CertificateKeyPair,
items: CertificateKeyPair[],
): boolean => {
let selected = this.instance?.signingKey === item.pk;
if (!this.instance && items.length === 1) {
selected = true;
}
return selected;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">${t`Key used to sign the tokens.`}</p>
</ak-form-element-horizontal>
</div>
@ -351,21 +366,21 @@ ${this.instance?.redirectUris}</textarea
?required=${true}
name="issuerMode"
>
<select class="pf-c-form-control">
<option
value="${IssuerModeEnum.PerProvider}"
?selected=${this.instance?.issuerMode ===
IssuerModeEnum.PerProvider}
>
${t`Each provider has a different issuer, based on the application slug.`}
</option>
<option
value="${IssuerModeEnum.Global}"
?selected=${this.instance?.issuerMode === IssuerModeEnum.Global}
>
${t`Same identifier is used for all providers`}
</option>
</select>
<ak-radio
.options=${[
{
label: t`Each provider has a different issuer, based on the application slug`,
value: IssuerModeEnum.PerProvider,
default: true,
},
{
label: t`Same identifier is used for all providers`,
value: IssuerModeEnum.Global,
},
]}
.value=${this.instance?.issuerMode}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Configure how the issuer field of the ID Token should be filled.`}
</p>

View File

@ -3,6 +3,7 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { t } from "@lingui/macro";
@ -19,9 +20,13 @@ import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-
import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
import {
CertificateKeyPair,
CryptoApi,
CryptoCertificatekeypairsListRequest,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
PropertymappingsApi,
ProvidersApi,
ProxyMode,
@ -274,6 +279,8 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
${t`Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}
</p>
</ak-form-element-horizontal>`;
case ProxyMode.UnknownDefaultOpenApi:
return html`<p>${t`Unknown proxy mode`}</p>`;
}
}
@ -292,26 +299,32 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
return flow.pk === this.instance?.authorizationFlow;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
@ -337,35 +350,35 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
<span slot="header">${t`Advanced protocol settings`}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Certificate`} name="certificate">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.certificate === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
hasKey: true,
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.certificate === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.certificate || undefined)}
?selected=${this.instance?.certificate !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return item.pk === this.instance?.certificate;
}}
?blankable=${true}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
<select class="pf-c-form-control" multiple>

View File

@ -47,6 +47,8 @@ export function ModeToLabel(action?: ProxyMode): string {
return t`Forward auth (single application)`;
case ProxyMode.ForwardDomain:
return t`Forward auth (domain-level)`;
case ProxyMode.UnknownDefaultOpenApi:
return t`Unknown proxy mode`;
}
}
@ -57,6 +59,8 @@ export function isForward(mode: ProxyMode): boolean {
case ProxyMode.ForwardSingle:
case ProxyMode.ForwardDomain:
return true;
case ProxyMode.UnknownDefaultOpenApi:
return false;
}
}

View File

@ -2,6 +2,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { t } from "@lingui/macro";
@ -12,12 +14,18 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CertificateKeyPair,
CryptoApi,
CryptoCertificatekeypairsListRequest,
DigestAlgorithmEnum,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
PropertymappingsApi,
PropertymappingsSamlListRequest,
ProvidersApi,
SAMLPropertyMapping,
SAMLProvider,
SignatureAlgorithmEnum,
SpBindingEnum,
@ -67,26 +75,32 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option
value=${ifDefined(flow.pk)}
?selected=${this.instance?.authorizationFlow === flow.pk}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
return flow.pk === this.instance?.authorizationFlow;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>
@ -117,20 +131,21 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
?required=${true}
name="spBinding"
>
<select class="pf-c-form-control">
<option
value=${SpBindingEnum.Redirect}
?selected=${this.instance?.spBinding === SpBindingEnum.Redirect}
>
${t`Redirect`}
</option>
<option
value=${SpBindingEnum.Post}
?selected=${this.instance?.spBinding === SpBindingEnum.Post}
>
${t`Post`}
</option>
</select>
<ak-radio
.options=${[
{
label: t`Redirect`,
value: SpBindingEnum.Redirect,
default: true,
},
{
label: t`Post`,
value: SpBindingEnum.Post,
},
]}
.value=${this.instance?.spBinding}
>
</ak-radio>
<p class="pf-c-form__helper-text">
${t`Determines how authentik sends the response back to the Service Provider.`}
</p>
@ -149,35 +164,35 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
<span slot="header"> ${t`Advanced protocol settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Signing Certificate`} name="signingKp">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.signingKp === undefined}>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
hasKey: true,
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.signingKp === key.pk}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.signingKp || undefined)}
?selected=${this.instance?.signingKp !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return item.pk === this.instance?.signingKp;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Certificate used to sign outgoing Responses going to the Service Provider.`}
</p>
@ -186,38 +201,35 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
label=${t`Verification Certificate`}
name="verificationKp"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.verificationKp === undefined}
>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
return html`<option
value=${ifDefined(key.pk)}
?selected=${this.instance?.verificationKp ===
key.pk}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.verificationKp || undefined)}
?selected=${this.instance?.verificationKp !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return item.pk === this.instance?.verificationKp;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.`}
</p>
@ -268,32 +280,35 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
label=${t`NameID Property Mapping`}
name="nameIdMapping"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.nameIdMapping === undefined}
>
---------
</option>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsSamlList({
ordering: "saml_name",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${this.instance?.nameIdMapping ===
mapping.pk}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<SAMLPropertyMapping[]> => {
const args: PropertymappingsSamlListRequest = {
ordering: "saml_name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSamlList(args);
return items.results;
}}
.renderElement=${(item: SAMLPropertyMapping): string => {
return item.name;
}}
.value=${(
item: SAMLPropertyMapping | undefined,
): string | undefined => {
return item?.pk;
}}
.selected=${(item: SAMLPropertyMapping): boolean => {
return this.instance?.nameIdMapping === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.`}
</p>
@ -353,81 +368,62 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
?required=${true}
name="digestAlgorithm"
>
<select class="pf-c-form-control">
<option
value=${DigestAlgorithmEnum._200009Xmldsigsha1}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200009Xmldsigsha1}
>
${t`SHA1`}
</option>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha256}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha256 ||
this.instance?.digestAlgorithm === undefined}
>
${t`SHA256`}
</option>
<option
value=${DigestAlgorithmEnum._200104XmldsigMoresha384}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104XmldsigMoresha384}
>
${t`SHA384`}
</option>
<option
value=${DigestAlgorithmEnum._200104Xmlencsha512}
?selected=${this.instance?.digestAlgorithm ===
DigestAlgorithmEnum._200104Xmlencsha512}
>
${t`SHA512`}
</option>
</select>
<ak-radio
.options=${[
{
label: "SHA1",
value: DigestAlgorithmEnum._200009Xmldsigsha1,
},
{
label: "SHA256",
value: DigestAlgorithmEnum._200104Xmlencsha256,
default: true,
},
{
label: "SHA384",
value: DigestAlgorithmEnum._200104XmldsigMoresha384,
},
{
label: "SHA512",
value: DigestAlgorithmEnum._200104Xmlencsha512,
},
]}
.value=${this.instance?.digestAlgorithm}
>
</ak-radio>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Signature algorithm`}
?required=${true}
name="signatureAlgorithm"
>
<select class="pf-c-form-control">
<option
value=${SignatureAlgorithmEnum._200009XmldsigrsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigrsaSha1}
>
${t`RSA-SHA1`}
</option>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha256}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha256 ||
this.instance?.signatureAlgorithm === undefined}
>
${t`RSA-SHA256`}
</option>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha384}
>
${t`RSA-SHA384`}
</option>
<option
value=${SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200104XmldsigMorersaSha512}
>
${t`RSA-SHA512`}
</option>
<option
value=${SignatureAlgorithmEnum._200009XmldsigdsaSha1}
?selected=${this.instance?.signatureAlgorithm ===
SignatureAlgorithmEnum._200009XmldsigdsaSha1}
>
${t`DSA-SHA1`}
</option>
</select>
<ak-radio
.options=${[
{
label: "RSA-SHA1",
value: SignatureAlgorithmEnum._200009XmldsigrsaSha1,
},
{
label: "RSA-SHA256",
value: SignatureAlgorithmEnum._200104XmldsigMorersaSha256,
default: true,
},
{
label: "RSA-SHA384",
value: SignatureAlgorithmEnum._200104XmldsigMorersaSha384,
},
{
label: "RSA-SHA512",
value: SignatureAlgorithmEnum._200104XmldsigMorersaSha512,
},
{
label: "DSA-SHA1",
value: SignatureAlgorithmEnum._200009XmldsigdsaSha1,
},
]}
.value=${this.instance?.signatureAlgorithm}
>
</ak-radio>
</ak-form-element-horizontal>
</div>
</ak-form-group>

View File

@ -2,16 +2,18 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import {
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
ProvidersApi,
SAMLProvider,
} from "@goauthentik/api";
@ -22,7 +24,6 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
return t`Successfully imported provider.`;
}
// eslint-disable-next-line
send = (data: SAMLProvider): Promise<void> => {
const file = this.getFormFiles()["metadata"];
if (!file) {
@ -45,23 +46,29 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
?required=${true}
name="authorizationFlow"
>
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
})
.then((flows) => {
return flows.results.map((flow) => {
return html`<option value=${flow.slug}>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.slug;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow used when authorizing this provider.`}
</p>

View File

@ -1,9 +1,9 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/SearchSelect";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -13,9 +13,11 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CertificateKeyPair,
CoreApi,
CoreGroupsListRequest,
CryptoApi,
CryptoCertificatekeypairsListRequest,
Group,
LDAPSource,
LDAPSourceRequest,
@ -149,39 +151,35 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
label=${t`TLS Verification Certificate`}
name="peerCertificate"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.peerCertificate === undefined}
>
---------
</option>
${until(
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsList({
ordering: "name",
includeDetails: false,
})
.then((keys) => {
return keys.results.map((key) => {
const selected =
this.instance?.peerCertificate === key.pk;
return html`<option
value=${ifDefined(key.pk)}
?selected=${selected}
>
${key.name}
</option>`;
});
}),
html`<option
value=${ifDefined(this.instance?.peerCertificate || undefined)}
?selected=${this.instance?.peerCertificate !== undefined}
>
${t`Loading...`}
</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return item.pk === this.instance?.peerCertificate;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`When connecting to an LDAP Server with TLS, certificates are not checked by default. Specify a keypair to validate the remote certificate.`}
</p>
@ -310,7 +308,6 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
<span slot="header"> ${t`Additional settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Group`} name="syncParentGroup">
<!-- @ts-ignore -->
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {

View File

@ -5,6 +5,7 @@ import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -15,8 +16,10 @@ import { until } from "lit/directives/until.js";
import {
CapabilitiesEnum,
Flow,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
OAuthSource,
OAuthSourceRequest,
ProviderTypeEnum,
@ -399,42 +402,43 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
?required=${true}
name="authenticationFlow"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.authenticationFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation:
FlowsInstancesListDesignationEnum.Authentication,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.authenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.authenticationFlow &&
flow.slug === "default-source-authentication"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authentication,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(
args,
);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
let selected = this.instance?.authenticationFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.authenticationFlow &&
flow.slug === "default-source-authentication"
) {
selected = true;
}
return selected;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow to use when authenticating existing users.`}
</p>
@ -444,41 +448,43 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
?required=${true}
name="enrollmentFlow"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.enrollmentFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Enrollment,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected =
this.instance?.enrollmentFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.enrollmentFlow &&
flow.slug === "default-source-enrollment"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Enrollment,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(
args,
);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return flow.slug;
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
let selected = this.instance?.enrollmentFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.enrollmentFlow &&
flow.slug === "default-source-enrollment"
) {
selected = true;
}
return selected;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${t`Flow to use when enrolling new users.`}
</p>

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