Compare commits

..

208 Commits

Author SHA1 Message Date
220d21c3e0 release: 2022.5.1 2022-05-20 19:34:45 +02:00
84e74bc21e website/docs: final 2022.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-20 19:34:43 +02:00
ec15060c84 web/admin: add button to empty state to clear search (#2911) 2022-05-20 18:01:26 +02:00
334898ae23 website/integrations: Fix Gitea integration manual steps (#2910) 2022-05-20 16:47:39 +02:00
b43df2ae27 stages/identification: redirect with QS to keep next parameters (#2909)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-20 16:10:10 +02:00
a52638d898 internal: fix typo in session name constant
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-20 10:10:29 +02:00
5bc893b890 build(deps): bump @babel/plugin-transform-runtime from 7.17.12 to 7.18.0 in /web (#2904) 2022-05-20 09:45:53 +02:00
fe5d9e4cd2 build(deps): bump @babel/core from 7.17.12 to 7.18.0 in /web (#2903) 2022-05-20 09:45:27 +02:00
a7442e0043 build(deps): bump @formatjs/intl-listformat from 6.5.3 to 7.0.1 in /web (#2905) 2022-05-20 09:45:06 +02:00
8103bbf9af build(deps): bump rollup from 2.73.0 to 2.74.1 in /web (#2906) 2022-05-20 09:44:50 +02:00
056b90b590 build(deps): bump @babel/preset-env from 7.17.12 to 7.18.0 in /web (#2907) 2022-05-20 09:44:37 +02:00
70221e3d14 web: fix labels in forms for dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-19 22:03:03 +02:00
d570feffac flows: add types to diagrams (#2902)
* add policy and stage types to diagram

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

* show policies bound to the root flow

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

* fix get_build_hash being empty

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

* update tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-19 20:50:28 +02:00
3d52266773 flows: handle missing initial_data in challenge
AUTHENTIK-1HK

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-19 20:31:28 +02:00
7bdecd2ee6 stages/user_write: dynamic groups (#2901)
* stages/user_write: add dynamic groups

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

* simplify functions

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-19 20:28:16 +02:00
a500ff28ac website/integrations: update proxmox docs (#2899)
Signed-off-by: Victor Roest <victor@xirion.net>
2022-05-19 20:23:27 +02:00
263bcae050 web/admin: improve empty state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/elements/table/TablePage.ts
#	web/src/pages/applications/ApplicationListPage.ts
2022-05-19 10:21:46 +02:00
8691a79204 build(deps): bump postcss from 8.4.13 to 8.4.14 in /website (#2900)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.13 to 8.4.14.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.13...8.4.14)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-19 09:29:30 +02:00
3b0b6dcf29 web: fix display of radio buttons on wizard pages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-18 23:09:02 +02:00
11f7935155 providers/oauth2: use regex to check redirect URI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2799
2022-05-18 21:22:27 +02:00
450a26d1b5 build(deps): bump @typescript-eslint/parser from 5.24.0 to 5.25.0 in /web (#2895)
build(deps): bump @typescript-eslint/parser in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-18 09:51:51 +02:00
3e42c1bad4 build(deps): bump @typescript-eslint/eslint-plugin from 5.24.0 to 5.25.0 in /web (#2896)
build(deps): bump @typescript-eslint/eslint-plugin in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-18 09:50:15 +02:00
5abbb7657b build(deps): bump lit from 2.2.3 to 2.2.4 in /web (#2897)
Bumps [lit](https://github.com/lit/lit/tree/HEAD/packages/lit) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/lit/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit@2.2.4/packages/lit)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-18 09:50:03 +02:00
75b0fb3393 sources/oauth: migrate twitter to oauth2 (#2893) 2022-05-18 00:03:02 +02:00
538c2ca4d3 stages/authenticator_*: directly save devices into db instead of session to prevent race conditions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-17 10:02:30 +02:00
5080840ed9 admin: ensure disable_update_check is set to false for tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-17 10:00:26 +02:00
eded9bfb2d build(deps): bump @babel/preset-typescript from 7.16.7 to 7.17.12 in /web (#2885)
build(deps): bump @babel/preset-typescript in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:34:09 +02:00
b3a43ae37c build(deps): bump @typescript-eslint/parser from 5.23.0 to 5.24.0 in /web (#2884)
build(deps): bump @typescript-eslint/parser in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:30:24 +02:00
dc78746825 build(deps): bump @babel/preset-env from 7.17.10 to 7.17.12 in /web (#2881)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.17.10 to 7.17.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.12/packages/babel-preset-env)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:29:58 +02:00
3c6828cbba Translate '/web/src/locales/en.po' in 'zh_CN' (#2878)
Translate /web/src/locales/en.po in zh_CN

translation completed for the source file '/web/src/locales/en.po'
on the 'zh_CN' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-17 09:29:45 +02:00
26646264dc Translate '/web/src/locales/en.po' in 'zh-Hans' (#2879)
Translate /web/src/locales/en.po in zh-Hans

translation completed for the source file '/web/src/locales/en.po'
on the 'zh-Hans' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-17 09:29:36 +02:00
f7ecfdd4b6 build(deps): bump @typescript-eslint/eslint-plugin from 5.23.0 to 5.24.0 in /web (#2883)
build(deps): bump @typescript-eslint/eslint-plugin in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:29:20 +02:00
967c80069b build(deps): bump @babel/core from 7.17.10 to 7.17.12 in /web (#2882)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.17.10 to 7.17.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.12/packages/babel-core)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:28:57 +02:00
f8b0c071b7 build(deps): bump @babel/plugin-transform-runtime from 7.17.10 to 7.17.12 in /web (#2886)
build(deps): bump @babel/plugin-transform-runtime in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:28:40 +02:00
221ab47410 build(deps): bump actions/setup-node from 3.1.1 to 3.2.0 (#2887)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:27:50 +02:00
ffe162214f build(deps): bump @babel/plugin-proposal-decorators from 7.17.9 to 7.17.12 in /web (#2888)
build(deps): bump @babel/plugin-proposal-decorators in /web

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:27:24 +02:00
ad9d8d26ed build(deps): bump paramiko from 2.10.4 to 2.11.0 (#2889)
Bumps [paramiko](https://github.com/paramiko/paramiko) from 2.10.4 to 2.11.0.
- [Release notes](https://github.com/paramiko/paramiko/releases)
- [Changelog](https://github.com/paramiko/paramiko/blob/main/NEWS)
- [Commits](https://github.com/paramiko/paramiko/compare/2.10.4...2.11.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-17 09:27:07 +02:00
35402ada17 website/docs: fix missing new name attribute for invitations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-16 20:40:24 +02:00
086a44bdbd web/user: add message upon source connection deletion
closes #2522

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-16 20:28:23 +02:00
6494a0352f Translate '/web/src/locales/en.po' in 'zh-Hans' (#2877) 2022-05-16 09:20:20 +02:00
ca1fb737a8 Translate '/web/src/locales/en.po' in 'zh_CN' (#2876) 2022-05-16 09:20:02 +02:00
9e91a0a85d build(deps): bump github.com/prometheus/client_golang from 1.12.1 to 1.12.2 (#2875) 2022-05-16 09:19:36 +02:00
4e68fe2fea build(deps): bump goauthentik.io/api/v3 from 3.2022041.6 to 3.2022041.10 (#2874) 2022-05-16 09:19:26 +02:00
a36eab81eb build(deps-dev): bump pylint from 2.13.8 to 2.13.9 (#2873) 2022-05-16 09:19:09 +02:00
215b2a3224 build(deps): bump @patternfly/patternfly from 4.192.1 to 4.194.4 in /web (#2872) 2022-05-16 09:17:43 +02:00
4c3f8e446f build(deps): bump yaml from 2.0.1 to 2.1.0 in /web (#2871) 2022-05-16 09:17:31 +02:00
4b9922e5b1 build(deps): bump rollup from 2.72.1 to 2.73.0 in /web (#2870) 2022-05-16 09:17:17 +02:00
6324521424 build(deps): bump react-before-after-slider-component from 1.1.2 to 1.1.3 in /website (#2869) 2022-05-16 09:17:02 +02:00
d6b18f2833 web: Update Web API Client version (#2868) 2022-05-16 01:13:29 +02:00
333e58ce2f flows/layouts (#2867) 2022-05-16 01:10:23 +02:00
699d3ca067 ci: exclude confirmed issues from stale
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-15 23:16:52 +02:00
296779ddf1 providers/ldap: remove technical preview disclaimer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-15 17:33:03 +02:00
8669f498f1 web: Update Web API Client version (#2866)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-05-14 22:43:55 +02:00
4de2ac3248 events: add task to expire seen notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 22:41:50 +02:00
eb4dce91c3 events: add user filter to notifications
as superuser all notifications are returned regardless of permission so we need to filter

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 22:31:13 +02:00
c64a99345b web: fix scrolling issue on library page
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 21:58:42 +02:00
2e174a1be5 website/docs: update 2022.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 21:48:28 +02:00
11ef500475 web: Update Web API Client version (#2865)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-05-14 21:38:38 +02:00
d4fd6153c8 api: fix OwnerFilter filtering out objects for superusers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 21:36:00 +02:00
85b6bfbe5f sources: fix parent serializer for user connections
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 21:26:02 +02:00
5ddd138c97 web: update default flow background
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 20:08:59 +02:00
5644d5f3f7 stages/authenticator_totp: fix key error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 19:57:00 +02:00
be06adcb59 web: lazy load parts of interfaces (#2864) 2022-05-14 17:07:37 +02:00
4da350ebfc web: fix dateTimeLocal() dropping local timezone
closes #2860

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 12:47:30 +02:00
f391c33bdf providers/oauth2: fix tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 12:41:40 +02:00
18f450bd49 root: enable sentry for tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 12:29:30 +02:00
ee36b7f3eb flows: move autosubmit stage into flows package
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 12:06:19 +02:00
f56d619243 web: Update Web API Client version (#2862) 2022-05-14 00:17:33 +02:00
a9a62bbfc8 providers/oauth2: use correct title based on flow context and translated
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 00:08:29 +02:00
ddd785898b providers/saml: add title attribute to autosubmit stage and render correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 00:08:14 +02:00
8ba45a5f6a providers/oauth2: don't create events before client_id can be verified to prevent spam
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-14 00:02:01 +02:00
7d41e6227b providers/oauth2: add tests for form_post, fix attrs not being flattened
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-13 23:52:50 +02:00
1363226697 providers/saml: make SAML metadata generation consistent
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-13 17:40:18 +02:00
25910bb577 build(deps-dev): bump coverage from 6.3.2 to 6.3.3 (#2857)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.3.2 to 6.3.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/6.3.2...6.3.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-13 09:44:07 +02:00
62e54a3a51 build(deps): bump pyjwt from 2.3.0 to 2.4.0 (#2858)
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.3.0...2.4.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-13 09:43:54 +02:00
5f5b4c962b ci: fix website mark
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-12 22:54:29 +02:00
4a9a19eacb website/integrations: clear up home-assistant integration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-12 21:40:58 +02:00
d4abf5621e providers/oauth2: add support for form_post response mode (#2818)
* Added request verification and parameter generation

* response_mode added to OAuthAuthorizationParams return

* Added class OauthPostFulfillmentStage
Check response_mode in initialization

* Corrected typo

* Removed separate class
Added handling for FORM_POST in create_response_uri
Added handling for FORM_POST in return class

* Fixed pylint error (trailing-whitespace)
Removed comment

* Reformatted authorize.py with black
2022-05-12 21:36:31 +02:00
1cb71b5217 web: fix invalid import paths
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-12 13:22:22 +02:00
a884f23855 build(deps-dev): bump pytest-randomly from 3.11.0 to 3.12.0 (#2855)
Bumps [pytest-randomly](https://github.com/pytest-dev/pytest-randomly) from 3.11.0 to 3.12.0.
- [Release notes](https://github.com/pytest-dev/pytest-randomly/releases)
- [Changelog](https://github.com/pytest-dev/pytest-randomly/blob/main/HISTORY.rst)
- [Commits](https://github.com/pytest-dev/pytest-randomly/compare/3.11.0...3.12.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-12 09:44:55 +02:00
421b003218 internal: set path on cookie for proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2305
2022-05-11 10:08:38 +02:00
25a4310bb1 internal: use Expires not MaxAge for LDAP session
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-11 10:04:32 +02:00
e897307548 Translate /web/src/locales/en.po in zh_CN (#2846)
translation completed for the source file '/web/src/locales/en.po'
on the 'zh_CN' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-11 10:00:09 +02:00
0fd959c5c0 Translate /web/src/locales/en.po in zh-Hans (#2847)
translation completed for the source file '/web/src/locales/en.po'
on the 'zh-Hans' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-11 10:00:00 +02:00
ce7d18798f build(deps): bump golang from 1.18.1-bullseye to 1.18.2-bullseye (#2848)
Bumps golang from 1.18.1-bullseye to 1.18.2-bullseye.

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-11 09:59:08 +02:00
be3b034cb8 build(deps): bump fuse.js from 6.6.1 to 6.6.2 in /web (#2849)
Bumps [fuse.js](https://github.com/krisk/Fuse) from 6.6.1 to 6.6.2.
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v6.6.1...v6.6.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-11 09:58:59 +02:00
9f674442d3 build(deps): bump goauthentik.io/api/v3 from 3.2022041.5 to 3.2022041.6 (#2850)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022041.5 to 3.2022041.6.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022041.5...v3.2022041.6)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-11 09:58:51 +02:00
c21793943d build(deps): bump sentry-sdk from 1.5.11 to 1.5.12 (#2851)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.5.11 to 1.5.12.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.5.11...1.5.12)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-11 09:57:50 +02:00
ec67b60219 policies/hibp: check in prompt data (#2845)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 23:47:36 +02:00
2fe553785e web: Update Web API Client version (#2844)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-05-10 21:08:47 +02:00
fd1d38f844 stages/authenticator_validate: remember (#2828)
* initial

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

* web: cleanup timedelta help

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

* add tooltip

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

* add tests

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

* assert response code in self.assertStageResponse

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

* add more tests, add duo

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

* add docs

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

* fix

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 21:05:22 +02:00
4d755dc0f6 build(deps): bump goauthentik.io/api/v3 from 3.2022041.4 to 3.2022041.5 (#2843)
* build(deps): bump goauthentik.io/api/v3 from 3.2022041.4 to 3.2022041.5

Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022041.4 to 3.2022041.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022041.4...v3.2022041.5)

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

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

* fix

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-05-10 20:33:19 +02:00
30c65f9e61 web: Update Web API Client version (#2840)
* web: Update Web API Client version

Signed-off-by: GitHub <noreply@github.com>

* fix

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

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 10:37:50 +02:00
3554406aa5 root: fix duplicate enum in api scheme
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 10:24:18 +02:00
5eeaac1ad9 ci: fix path for bumping web api client version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 10:19:15 +02:00
5a172abdb9 ci: allow manual triggering of web API release
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 10:12:47 +02:00
8f861d8ecb website/docs: Expanded documentation for SAML Federation w/ examples. (#2822)
* Expanded documentation for SAML Federation w/ examples.

* Added short section for setup on IDP side

* Formatting according to guidelines
Changed example section to table instead of headings

* Escaped <> for formatting

* Fixed quotation on <>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-10 10:11:10 +02:00
f9fdcd2d07 build(deps): bump @typescript-eslint/parser in /web (#2837)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.22.0 to 5.23.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.23.0/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-10 09:35:03 +02:00
ed58f21a21 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2838)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.22.0 to 5.23.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.23.0/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-10 09:33:50 +02:00
45af8eb4be build(deps): bump @docusaurus/preset-classic in /website (#2834)
Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 2.0.0-beta.18 to 2.0.0-beta.20.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.20/packages/docusaurus-preset-classic)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-09 21:33:04 +02:00
88573105a0 build(deps): bump @docusaurus/plugin-client-redirects in /website (#2835)
Bumps [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) from 2.0.0-beta.18 to 2.0.0-beta.20.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.20/packages/docusaurus-plugin-client-redirects)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-09 21:31:03 +02:00
f9469e3f99 website: format docs with prettier (#2833)
* run prettier

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

* add scim to comparison

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-09 21:22:41 +02:00
26d92d9259 build(deps): bump kubernetes from 23.3.0 to 23.6.0 (#2832) 2022-05-09 09:11:28 +02:00
9cb0d37d51 build(deps): bump rollup from 2.72.0 to 2.72.1 in /web (#2831) 2022-05-09 09:11:23 +02:00
5a25e1524a build(deps): bump eslint from 8.14.0 to 8.15.0 in /web (#2830) 2022-05-09 09:11:17 +02:00
9e1a518689 internal: add signal handler for SIGTERM
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 23:05:47 +02:00
cf5771dad3 Translate /web/src/locales/en.po in zh-Hans (#2827)
translation completed for the source file '/web/src/locales/en.po'
on the 'zh-Hans' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-08 19:43:59 +02:00
db5aafed36 Translate /web/src/locales/en.po in zh_CN (#2826)
translation completed for the source file '/web/src/locales/en.po'
on the 'zh_CN' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-08 19:43:49 +02:00
4b0324220a website/docs: prepare 2022.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 17:32:21 +02:00
0183d2c880 root: match client-go openapi generator version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 17:25:09 +02:00
c1fe18a261 ci: always generate Go API Client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 16:50:58 +02:00
ab2299ba1e outposts/ldap: cached bind (#2824)
* initial cached ldap bind support

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

* add web

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

* add docs

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

* clean up api generation

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

* use gh action for golangci-lint

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 16:48:53 +02:00
2678b381b9 web: Update Web API Client version (#2825)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-05-08 16:14:02 +02:00
d3ef7920cb root: add bind mode to schema
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-08 16:08:06 +02:00
860269acf0 root: set SESSION_SAVE_EVERY_REQUEST to enable sliding sessions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1878
2022-05-07 22:32:56 +02:00
d2bd177b8f Apply translations in zh_CN (#2735)
translation completed for the source file '/locale/en/LC_MESSAGES/django.po'
on the 'zh_CN' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-07 21:33:08 +02:00
32cc03832a Apply translations in zh-Hans (#2736)
translation completed for the source file '/locale/en/LC_MESSAGES/django.po'
on the 'zh-Hans' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-05-07 21:32:59 +02:00
948d2cbdca web/user: add missing checkbox element in user settings (#2762) 2022-05-07 21:32:11 +02:00
22026f0755 website/integration: add Weblate integration with SAML (#2786)
* added weblate integration

* added missing entry in sidebarIntegrations
2022-05-07 21:26:10 +02:00
a7a7b5aacb root: export poetry deps to requirements.txt so we don't need poetry … (#2823)
* root: export poetry deps to requirements.txt so we don't need poetry installed, removed packages we don't need anymore

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

* update docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-07 21:22:33 +02:00
03d5b9e7e9 build(deps): bump fuse.js from 6.6.0 to 6.6.1 in /web (#2810)
Bumps [fuse.js](https://github.com/krisk/Fuse) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v6.6.0...v6.6.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 10:09:19 +02:00
30c7e6c94c providers/oauth2: fixed typo (PROMPT_CONSNET => PROMPT_CONSENT) (#2819) 2022-05-06 10:09:09 +02:00
1ba96586f7 build(deps): bump rollup from 2.71.1 to 2.72.0 in /web (#2811)
Bumps [rollup](https://github.com/rollup/rollup) from 2.71.1 to 2.72.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.71.1...v2.72.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:57 +02:00
607f632515 build(deps): bump docker/login-action from 1 to 2 (#2812)
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:49 +02:00
58b46fbfcd build(deps): bump docker/setup-buildx-action from 1 to 2 (#2813)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:40 +02:00
9b53e26ab0 build(deps): bump docker/setup-qemu-action from 1.2.0 to 2.0.0 (#2814)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1.2.0 to 2.0.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1.2.0...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:28 +02:00
832d3175aa build(deps): bump docker/build-push-action from 2 to 3 (#2815)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 3.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:18 +02:00
ebea8369d6 build(deps-dev): bump selenium from 4.1.3 to 4.1.5 (#2816)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.1.3 to 4.1.5.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:53:07 +02:00
a8508aac99 build(deps): bump github.com/go-openapi/runtime from 0.24.0 to 0.24.1 (#2817)
Bumps [github.com/go-openapi/runtime](https://github.com/go-openapi/runtime) from 0.24.0 to 0.24.1.
- [Release notes](https://github.com/go-openapi/runtime/releases)
- [Commits](https://github.com/go-openapi/runtime/compare/v0.24.0...v0.24.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 09:52:25 +02:00
59df02b3b8 root: disable stdout capturing for tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-05 23:08:36 +02:00
f00657f217 website/integrations: update opnsense integration guidance (#2808)
* website/docs: update opnsense integration guidance

* website/docs: remove `-user` opnsense integration
2022-05-05 12:26:16 +02:00
110bc762a1 build(deps): bump sentry-sdk from 1.5.10 to 1.5.11 (#2806)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.5.10 to 1.5.11.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.5.10...1.5.11)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 10:16:57 +02:00
f35e5f79aa build(deps): bump fuse.js from 6.5.3 to 6.6.0 in /web (#2800)
Bumps [fuse.js](https://github.com/krisk/Fuse) from 6.5.3 to 6.6.0.
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v6.5.3...v6.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:31:32 +02:00
3f32109706 build(deps): bump @rollup/plugin-node-resolve in /web (#2801)
Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 13.2.1 to 13.3.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/node-resolve-v13.3.0/packages/node-resolve)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:31:14 +02:00
0f042f2e4a build(deps): bump @typescript-eslint/eslint-plugin in /web (#2802)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.21.0 to 5.22.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.22.0/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:31:02 +02:00
34d1eb140b build(deps): bump @typescript-eslint/parser in /web (#2803)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.21.0 to 5.22.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.22.0/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:28:13 +02:00
62f67aabe3 build(deps-dev): bump pylint from 2.13.7 to 2.13.8 (#2804)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.13.7 to 2.13.8.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.13.7...v2.13.8)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 10:49:18 +02:00
82c3eaa0f9 website/integrations: fix duplicate cn=
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-02 23:19:50 +02:00
31ede2ae1d build(deps): bump @babel/preset-env from 7.16.11 to 7.17.10 in /web (#2793) 2022-05-02 13:41:45 +02:00
54c672256f build(deps): bump postcss from 8.4.12 to 8.4.13 in /website (#2794) 2022-05-02 13:41:24 +02:00
5f47d46b6f build(deps): bump @babel/core from 7.17.9 to 7.17.10 in /web (#2795) 2022-05-02 13:40:57 +02:00
3f23bc0b85 build(deps): bump rollup from 2.70.2 to 2.71.1 in /web (#2796) 2022-05-02 13:40:44 +02:00
366142382b build(deps): bump @babel/plugin-transform-runtime in /web (#2797) 2022-05-02 13:40:23 +02:00
ddbe0aaf13 stages/user_delete: fix delete stage failing when pending user is not explicitly set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-05-01 13:59:33 +02:00
75320bf579 website/docs: add missing breaking change in prompt stages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-29 21:19:51 +02:00
15d8988569 Revert "lifecycle: use worker nr instead of process id to keep number of prometheus database files low"
This reverts commit 254249e38b.

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-29 18:14:04 +02:00
84930b4924 Revert "internal: fix high cpu when backend isnt healthy"
This reverts commit eb6cfd22a7.

Revert "root: handle JSON error in metrics too"

This reverts commit 1ede972222.

Revert "root: don't force multiprocess prometheus registry"

This reverts commit cd1d1b4402.

Revert "root: add error handling for prometheus view"

This reverts commit c0a883f76f.
2022-04-29 18:13:26 +02:00
1ede972222 root: handle JSON error in metrics too
this can happen when the worker is killed while writing metrics
2022-04-29 11:01:04 +00:00
cd1d1b4402 root: don't force multiprocess prometheus registry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-29 10:53:47 +02:00
79caba45cc build(deps): bump lit from 2.2.2 to 2.2.3 in /web (#2787)
Bumps [lit](https://github.com/lit/lit/tree/HEAD/packages/lit) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/lit/lit/releases)
- [Changelog](https://github.com/lit/lit/blob/main/packages/lit/CHANGELOG.md)
- [Commits](https://github.com/lit/lit/commits/lit@2.2.3/packages/lit)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 10:25:07 +02:00
c101357051 build(deps): bump typescript from 4.6.3 to 4.6.4 in /web (#2788)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.6.3 to 4.6.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.6.3...v4.6.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 10:24:57 +02:00
9bebb82bbf build(deps): bump webauthn from 1.5.0 to 1.5.2 (#2789)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 1.5.0 to 1.5.2.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v1.5.0...v1.5.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 10:24:46 +02:00
d95d2ca7fe build(deps): bump github.com/go-openapi/runtime from 0.23.3 to 0.24.0 (#2790)
Bumps [github.com/go-openapi/runtime](https://github.com/go-openapi/runtime) from 0.23.3 to 0.24.0.
- [Release notes](https://github.com/go-openapi/runtime/releases)
- [Commits](https://github.com/go-openapi/runtime/compare/v0.23.3...v0.24.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 10:24:30 +02:00
c0a883f76f root: add error handling for prometheus view
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-29 10:17:53 +02:00
eb6cfd22a7 internal: fix high cpu when backend isnt healthy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-29 10:03:40 +02:00
254249e38b lifecycle: use worker nr instead of process id to keep number of prometheus database files low
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2784
2022-04-28 23:09:39 +02:00
da28bb7d3c build(deps): bump @typescript-eslint/parser in /web (#2774) 2022-04-26 14:42:41 +02:00
391c1ff911 build(deps): bump @patternfly/patternfly from 4.185.1 to 4.192.1 in /web (#2773) 2022-04-26 14:42:25 +02:00
1d475d0982 build(deps): bump paramiko from 2.10.3 to 2.10.4 (#2777) 2022-04-26 14:41:31 +02:00
f92fa61101 build(deps): bump drf-spectacular from 0.22.0 to 0.22.1 (#2778) 2022-04-26 14:41:24 +02:00
ccca397a77 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2775) 2022-04-26 14:41:11 +02:00
162fd26f32 build(deps): bump github/codeql-action from 1 to 2 (#2776) 2022-04-26 14:41:02 +02:00
1d7a235766 build(deps): bump node from 16 to 18 (#2753)
Bumps node from 16 to 18.

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:30:08 +02:00
01a8deb77f build(deps): bump tslib from 2.3.1 to 2.4.0 in /web (#2754)
Bumps [tslib](https://github.com/Microsoft/tslib) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/Microsoft/tslib/releases)
- [Commits](https://github.com/Microsoft/tslib/compare/2.3.1...2.4.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:29:54 +02:00
cba770a551 build(deps): bump eslint from 8.13.0 to 8.14.0 in /web (#2764)
Bumps [eslint](https://github.com/eslint/eslint) from 8.13.0 to 8.14.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.13.0...v8.14.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:29:33 +02:00
c67afc4084 build(deps): bump @lingui/detect-locale from 3.13.2 to 3.13.3 in /web (#2765)
Bumps [@lingui/detect-locale](https://github.com/lingui/js-lingui) from 3.13.2 to 3.13.3.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.2...v3.13.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:29:19 +02:00
4ed30fa61e build(deps): bump @lingui/cli from 3.13.2 to 3.13.3 in /web (#2768)
Bumps [@lingui/cli](https://github.com/lingui/js-lingui) from 3.13.2 to 3.13.3.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.2...v3.13.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:29:06 +02:00
db16a0ffbe build(deps): bump @lingui/macro from 3.13.2 to 3.13.3 in /web (#2763)
Bumps [@lingui/macro](https://github.com/lingui/js-lingui) from 3.13.2 to 3.13.3.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.2...v3.13.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:28:37 +02:00
99ec355710 build(deps): bump @rollup/plugin-commonjs from 21.1.0 to 22.0.0 in /web (#2766)
Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs) from 21.1.0 to 22.0.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/commonjs-v22.0.0/packages/commonjs)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:27:53 +02:00
9e1882cebd build(deps): bump @types/chart.js from 2.9.36 to 2.9.37 in /web (#2767)
Bumps [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js) from 2.9.36 to 2.9.37.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:27:40 +02:00
80912cace0 build(deps): bump @lingui/core from 3.13.2 to 3.13.3 in /web (#2769)
Bumps [@lingui/core](https://github.com/lingui/js-lingui) from 3.13.2 to 3.13.3.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.13.2...v3.13.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:27:20 +02:00
0882894dc3 build(deps): bump country-flag-icons from 1.4.25 to 1.4.26 in /web (#2770)
Bumps [country-flag-icons](https://gitlab.com/catamphetamine/country-flag-icons) from 1.4.25 to 1.4.26.
- [Release notes](https://gitlab.com/catamphetamine/country-flag-icons/tags)
- [Changelog](https://gitlab.com/catamphetamine/country-flag-icons/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/catamphetamine/country-flag-icons/compare/v1.4.25...v1.4.26)

---
updated-dependencies:
- dependency-name: country-flag-icons
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:27:05 +02:00
c1582147d7 build(deps-dev): bump pytest from 7.1.1 to 7.1.2 (#2771)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.1 to 7.1.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.1...7.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-25 12:24:58 +02:00
ab8b37a899 events: fix ignored instances not being a tuple
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-25 11:19:58 +02:00
9077eff34d root: add silk and debugging views
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-21 22:38:32 +02:00
2399fa456b policies: fix current user not being set in server-side policy deny
closes #2039

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-21 22:30:27 +02:00
c8c69a9a56 build(deps): bump codemirror from 5.65.2 to 5.65.3 in /web (#2750)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.65.2 to 5.65.3.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.65.2...5.65.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-21 10:54:37 +02:00
1258f3bba2 build(deps-dev): bump pylint from 2.13.5 to 2.13.7 (#2751)
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.13.5 to 2.13.7.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.13.5...v2.13.7)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-21 10:54:29 +02:00
5488120e84 build(deps): bump duo-client from 4.3.2 to 4.4.0 (#2752)
Bumps [duo-client](https://github.com/duosecurity/duo_client_python) from 4.3.2 to 4.4.0.
- [Release notes](https://github.com/duosecurity/duo_client_python/releases)
- [Commits](https://github.com/duosecurity/duo_client_python/compare/4.3.2...4.4.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-21 10:54:18 +02:00
0b4ac54363 *: default to max 60 for fqdn_rand
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-20 20:07:25 +02:00
1a1434bfda *: decrease frequency of background tasks, smear tasks based on name and fqdn
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2159
2022-04-20 18:43:40 +02:00
1328c3e62c build(deps): bump @typescript-eslint/eslint-plugin in /web (#2746)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.19.0 to 5.20.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.20.0/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-19 09:39:15 +02:00
1800b62cd6 build(deps): bump @typescript-eslint/parser in /web (#2747)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.19.0 to 5.20.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.20.0/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-19 09:36:48 +02:00
32fa4c9fcb build(deps): bump @rollup/plugin-commonjs from 21.0.3 to 21.1.0 in /web (#2740)
Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs) from 21.0.3 to 21.1.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/commonjs-v21.1.0/packages/commonjs)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:23:22 +02:00
15f0045a00 build(deps): bump rollup from 2.70.1 to 2.70.2 in /web (#2738)
Bumps [rollup](https://github.com/rollup/rollup) from 2.70.1 to 2.70.2.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.70.1...v2.70.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:22:32 +02:00
ac2211d9da build(deps): bump moment from 2.29.2 to 2.29.3 in /web (#2737)
Bumps [moment](https://github.com/moment/moment) from 2.29.2 to 2.29.3.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/2.29.3/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.2...2.29.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:22:24 +02:00
cbd5b0dbfd build(deps): bump yaml from 2.0.0 to 2.0.1 in /web (#2739)
Bumps [yaml](https://github.com/eemeli/yaml) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.0.0...v2.0.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:22:16 +02:00
8e4896d261 build(deps): bump @rollup/plugin-node-resolve in /web (#2741)
Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/node-resolve-v13.2.1/packages/node-resolve)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:22:01 +02:00
9481df619a build(deps): bump sentry-sdk from 1.5.9 to 1.5.10 (#2742)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.5.9 to 1.5.10.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.5.9...1.5.10)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 13:21:52 +02:00
d283a5236c core: add custom shell command which imports all models and creates events for model events
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-17 18:14:57 +02:00
6add88654e build(deps): bump goauthentik.io/api/v3 from 3.2022041.2 to 3.2022041.3 (#2734)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022041.2 to 3.2022041.3.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022041.2...v3.2022041.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-17 17:30:40 +02:00
e4486b98fc web: Update Web API Client version (#2733)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-17 17:05:43 +02:00
778065f468 core: add flag to globally disable impersonation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-17 16:52:55 +02:00
70794d79dd sources/oauth: Fix wording for OAuth source names (#2732) 2022-04-17 16:40:10 +02:00
6e5ac4bffc website/docs: add missing redis port to configuration page (#2731)
Added the `AUTHENTIK_REDIS__PORT` to the documentation.
2022-04-17 16:40:00 +02:00
4bab42fb58 Translate /locale/en/LC_MESSAGES/django.po in de (#2727) 2022-04-15 23:45:53 +02:00
c97823fe49 build(deps): bump goauthentik.io/api/v3 from 3.2022041.1 to 3.2022041.2 (#2726) 2022-04-15 11:09:56 +02:00
a3bb5d89cc events: fix created events only being logged as debug level
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-14 22:37:30 +02:00
f4f9f525d7 providers/oauth2: include application in login event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-14 22:36:45 +02:00
555525ea9d build(deps): bump async from 2.6.3 to 2.6.4 in /website (#2725)
* root: use npm ci

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

* build(deps): bump async from 2.6.3 to 2.6.4 in /website

Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

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

Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 22:35:53 +02:00
e455e20312 root: use npm ci
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-14 22:33:36 +02:00
4c14e88a25 flows: pin dependency in migration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-04-14 22:28:26 +02:00
7561ea15de providers/oauth2: add additional tracing to token view 2022-04-14 16:48:17 +00:00
8242b09394 flows: handle flow title formatting error better, add user to flow title context 2022-04-14 13:56:20 +00:00
6f0fa731c0 build(deps): bump golang from 1.18.0-bullseye to 1.18.1-bullseye (#2720)
Bumps golang from 1.18.0-bullseye to 1.18.1-bullseye.

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 09:58:38 +02:00
576bb013ed build(deps): bump @rollup/plugin-typescript from 8.3.1 to 8.3.2 in /web (#2721)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins/tree/HEAD/packages/typescript) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/typescript/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/typescript-v8.3.2/packages/typescript)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 09:58:24 +02:00
aefedfb836 build(deps): bump goauthentik.io/api/v3 from 3.2022033.11 to 3.2022041.1 (#2722)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2022033.11 to 3.2022041.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2022033.11...v3.2022041.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 09:57:46 +02:00
4295ddb671 web: Update Web API Client version (#2718)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-04-13 10:34:47 +02:00
385 changed files with 11173 additions and 7920 deletions

View File

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

2
.github/stale.yml vendored
View File

@ -8,6 +8,8 @@ exemptLabels:
- security
- pr_wanted
- enhancement
- bug/confirmed
- enhancement/confirmed
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had

View File

@ -136,9 +136,9 @@ jobs:
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web
run: |
cd web
npm i
npm ci
npm run build
- name: run e2e
run: |
@ -169,9 +169,9 @@ jobs:
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
working-directory: web/
run: |
cd web
npm i
npm ci
npm run build
- name: run e2e
run: |
@ -207,23 +207,23 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: prepare variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
uses: ./.github/actions/docker-setup
- name: Login to Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |

View File

@ -18,18 +18,16 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- name: Run linter
- name: Prepare and generate API
run: |
# Create folder structure for go embeds
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.43 \
golangci-lint run -v --timeout 200s
- name: Generate API
run: make gen-client-go
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
test-unittest:
runs-on: ubuntu-latest
steps:
@ -37,6 +35,8 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- name: Generate API
run: make gen-client-go
- name: Go unittests
run: |
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./...
@ -63,23 +63,25 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: prepare variables
id: ev
uses: ./.github/actions/docker-setup
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
- name: Login to Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate API
run: make gen-client-go
- name: Building Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
@ -108,15 +110,17 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Generate API
run: make gen-client-go
- name: Build web
working-directory: web/
run: |
cd web
npm install
npm ci
npm run build-proxy
- name: Build outpost
run: |

View File

@ -15,56 +15,50 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-web
run: make gen-client-web
- name: Eslint
run: |
cd web
npm run lint
working-directory: web/
run: npm run lint
lint-prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-web
run: make gen-client-web
- name: prettier
run: |
cd web
npm run prettier-check
working-directory: web/
run: npm run prettier-check
lint-lit-analyse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-web
run: make gen-client-web
- name: lit-analyse
run: |
cd web
npm run lit-analyse
working-directory: web/
run: npm run lit-analyse
ci-web-mark:
needs:
- lint-eslint
@ -79,17 +73,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- working-directory: web/
run: npm ci
- name: Generate API
run: make gen-web
run: make gen-client-web
- name: build
run: |
cd web
npm run build
working-directory: web/
run: npm run build

33
.github/workflows/ci-website.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: authentik-ci-website
on:
push:
branches:
- master
- next
- version-*
pull_request:
branches:
- master
jobs:
lint-prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: website/package-lock.json
- working-directory: website/
run: npm ci
- name: prettier
working-directory: website/
run: npm run prettier-check
ci-website-mark:
needs:
- lint-prettier
runs-on: ubuntu-latest
steps:
- run: echo mark

View File

@ -32,7 +32,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -43,7 +43,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@ -11,28 +11,28 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Docker Login Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2022.4.1,
beryju/authentik:2022.5.1,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2022.4.1,
ghcr.io/goauthentik/server:2022.5.1,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
@ -50,28 +50,28 @@ jobs:
with:
go-version: "^1.17"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Docker Login Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-${{ matrix.type }}:2022.4.1,
beryju/authentik-${{ matrix.type }}:2022.5.1,
beryju/authentik-${{ matrix.type }}:latest,
ghcr.io/goauthentik/${{ matrix.type }}:2022.4.1,
ghcr.io/goauthentik/${{ matrix.type }}:2022.5.1,
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
@ -91,15 +91,15 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "^1.17"
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Build web
working-directory: web/
run: |
cd web
npm install
npm ci
npm run build-proxy
- name: Build outpost
run: |
@ -152,7 +152,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2022.4.1
version: authentik@2022.5.1
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -4,29 +4,30 @@ on:
branches: [ master ]
paths:
- 'schema.yml'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3.1.1
- uses: actions/setup-node@v3.2.0
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- name: Generate API Client
run: make gen-web
run: make gen-client-web
- name: Publish package
working-directory: gen-ts-api/
run: |
cd web-api/
npm i
npm ci
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
- name: Upgrade /web
working-directory: web/
run: |
cd web/
export VERSION=`node -e 'console.log(require("../web-api/package.json").version)'`
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
npm i @goauthentik/api@$VERSION
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4

3
.gitignore vendored
View File

@ -202,5 +202,4 @@ media/
*mmdb
.idea/
/api/
/web-api/
/gen-*/

View File

@ -1,22 +1,35 @@
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as website-builder
FROM --platform=${BUILDPLATFORM} docker.io/node:18 as website-builder
COPY ./website /work/website/
ENV NODE_ENV=production
RUN cd /work/website && npm i && npm run build-docs-only
WORKDIR /work/website
RUN npm ci && npm run build-docs-only
# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as web-builder
FROM --platform=${BUILDPLATFORM} docker.io/node:18 as web-builder
COPY ./web /work/web/
COPY ./website /work/website/
ENV NODE_ENV=production
RUN cd /work/web && npm i && npm run build
WORKDIR /work/web
RUN npm ci && npm run build
# Stage 3: Build go proxy
FROM docker.io/golang:1.18.0-bullseye AS builder
# Stage 3: Poetry to requirements.txt export
FROM docker.io/python:3.10.4-slim-bullseye AS poetry-locker
WORKDIR /work
COPY ./pyproject.toml /work
COPY ./poetry.lock /work
RUN pip install --no-cache-dir poetry && \
poetry export -f requirements.txt --output requirements.txt && \
poetry export -f requirements.txt --dev --output requirements-dev.txt
# Stage 4: Build go proxy
FROM docker.io/golang:1.18.2-bullseye AS builder
WORKDIR /work
@ -31,7 +44,7 @@ COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 4: Run
# Stage 5: Run
FROM docker.io/python:3.10.4-slim-bullseye
LABEL org.opencontainers.image.url https://goauthentik.io
@ -43,19 +56,18 @@ WORKDIR /
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
COPY ./pyproject.toml /
COPY ./poetry.lock /
COPY --from=poetry-locker /work/requirements.txt /
COPY --from=poetry-locker /work/requirements-dev.txt /
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl ca-certificates gnupg git runit libpq-dev \
postgresql-client build-essential libxmlsec1-dev \
pkg-config libmaxminddb0 && \
pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-dev && \
rm -rf ~/.cache/pypoetry && \
apt-get remove --purge -y build-essential git && \
# Required for installing pip packages
apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev && \
# Required for runtime
apt-get install -y --no-install-recommends libxmlsec1-openssl libmaxminddb0 && \
# Required for other things
apt-get install -y --no-install-recommends runit && \
pip install --no-cache-dir -r /requirements.txt && \
apt-get remove --purge -y build-essential pkg-config libxmlsec1-dev && \
apt-get autoremove --purge -y && \
apt-get clean && \
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \

View File

@ -18,6 +18,15 @@ test-e2e-rest:
test-go:
go test -timeout 0 -v -race -cover ./...
test-docker:
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
docker-compose pull -q
docker-compose up --no-start
docker-compose start postgresql redis
docker-compose run -u root server test
rm -f .env
test:
coverage run manage.py test authentik
coverage html
@ -52,21 +61,21 @@ gen-clean:
rm -rf web/api/src/
rm -rf api/
gen-web:
gen-client-web:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
openapitools/openapi-generator-cli:v6.0.0-beta generate \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/web-api \
-o /local/gen-ts-api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=@goauthentik/api,npmVersion=${NPM_VERSION}
mkdir -p web/node_modules/@goauthentik/api
\cp -fv scripts/web_api_readme.md web-api/README.md
cd web-api && npm i
\cp -rfv web-api/* web/node_modules/@goauthentik/api
\cp -fv scripts/web_api_readme.md gen-ts-api/README.md
cd gen-ts-api && npm i
\cp -rfv gen-ts-api/* web/node_modules/@goauthentik/api
gen-outpost:
gen-client-go:
wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O config.yaml
mkdir -p templates
wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O templates/README.mustache
@ -74,15 +83,15 @@ gen-outpost:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
openapitools/openapi-generator-cli:v6.0.0-beta generate \
openapitools/openapi-generator-cli:v5.2.1 generate \
-i /local/schema.yml \
-g go \
-o /local/api \
-o /local/gen-go-api \
-c /local/config.yaml
go mod edit -replace goauthentik.io/api=./api
go mod edit -replace goauthentik.io/api/v3=./gen-go-api
rm -rf config.yaml ./templates/
gen: gen-build gen-clean gen-web
gen: gen-build gen-clean gen-client-web
migrate:
python -m lifecycle.migrate
@ -90,11 +99,18 @@ migrate:
run:
go run -v cmd/server/main.go
web-watch:
cd web && npm run watch
#########################
## Web
#########################
web: web-lint-fix web-lint web-extract
web-install:
cd web && npm ci
web-watch:
cd web && npm run watch
web-lint-fix:
cd web && npm run prettier
@ -105,6 +121,21 @@ web-lint:
web-extract:
cd web && npm run extract
#########################
## Website
#########################
website: website-lint-fix
website-install:
cd website && npm ci
website-lint-fix:
cd website && npm run prettier
website-watch:
cd website && npm run watch
# These targets are use by GitHub actions to allow usage of matrix
# which makes the YAML File a lot smaller
@ -130,10 +161,8 @@ ci-pyright: ci--meta-debug
ci-pending-migrations: ci--meta-debug
./manage.py makemigrations --check
install:
install: web-install website-install
poetry install
cd web && npm i
cd website && npm i
a: install
tmux \

View File

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

View File

@ -1,10 +1,12 @@
"""authentik admin settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"admin_latest_version": {
"task": "authentik.admin.tasks.update_latest_version",
"schedule": crontab(minute="*/60"), # Run every hour
"schedule": crontab(minute=fqdn_rand("admin_latest_version"), hour="*"),
"options": {"queue": "authentik_scheduled"},
}
}

View File

@ -26,7 +26,7 @@ class TestAdminTasks(TestCase):
def test_version_valid_response(self):
"""Test Update checker with valid response"""
with Mocker() as mocker:
with Mocker() as mocker, CONFIG.patch("disable_update_check", False):
mocker.get("https://version.goauthentik.io/version.json", json=RESPONSE_VALID)
update_latest_version.delay().get()
self.assertEqual(cache.get(VERSION_CACHE_KEY), "99999999.9999999")

View File

@ -12,6 +12,8 @@ class OwnerFilter(BaseFilterBackend):
owner_key = "user"
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
if request.user.is_superuser:
return queryset
return queryset.filter(**{self.owner_key: request.user})

View File

@ -27,6 +27,7 @@ class Capabilities(models.TextChoices):
CAN_SAVE_MEDIA = "can_save_media"
CAN_GEO_IP = "can_geo_ip"
CAN_IMPERSONATE = "can_impersonate"
class ErrorReportingConfigSerializer(PassiveSerializer):
@ -63,6 +64,8 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_SAVE_MEDIA)
if GEOIP_READER.enabled:
caps.append(Capabilities.CAN_GEO_IP)
if CONFIG.y_bool("impersonation"):
caps.append(Capabilities.CAN_IMPERSONATE)
return caps
@extend_schema(responses={200: ConfigSerializer(many=False)})

View File

@ -22,11 +22,11 @@ from authentik.core.api.sources import SourceViewSet, UserSourceConnectionViewSe
from authentik.core.api.tokens import TokenViewSet
from authentik.core.api.users import UserViewSet
from authentik.crypto.api import CertificateKeyPairViewSet
from authentik.events.api.event import EventViewSet
from authentik.events.api.notification import NotificationViewSet
from authentik.events.api.notification_mapping import NotificationWebhookMappingViewSet
from authentik.events.api.notification_rule import NotificationRuleViewSet
from authentik.events.api.notification_transport import NotificationTransportViewSet
from authentik.events.api.events import EventViewSet
from authentik.events.api.notification_mappings import NotificationWebhookMappingViewSet
from authentik.events.api.notification_rules import NotificationRuleViewSet
from authentik.events.api.notification_transports import NotificationTransportViewSet
from authentik.events.api.notifications import NotificationViewSet
from authentik.flows.api.bindings import FlowStageBindingViewSet
from authentik.flows.api.flows import FlowViewSet
from authentik.flows.api.stages import StageViewSet

View File

@ -12,7 +12,7 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
from structlog.stdlib import get_logger
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Source, UserSourceConnection
@ -150,6 +150,6 @@ class UserSourceConnectionViewSet(
queryset = UserSourceConnection.objects.all()
serializer_class = UserSourceConnectionSerializer
permission_classes = [OwnerPermissions]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["pk"]

View File

@ -17,6 +17,7 @@ from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
extend_schema,
extend_schema_field,
inline_serializer,
@ -31,7 +32,6 @@ from rest_framework.serializers import (
ListSerializer,
ModelSerializer,
PrimaryKeyRelatedField,
Serializer,
ValidationError,
)
from rest_framework.viewsets import ModelViewSet
@ -351,8 +351,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
},
),
responses={
204: "",
400: "",
204: OpenApiResponse(description="Successfully changed password"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=True, methods=["POST"])
@ -410,8 +410,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
],
responses={
"204": Serializer(),
"404": Serializer(),
"204": OpenApiResponse(description="Successfully sent recover email"),
"404": OpenApiResponse(description="Bad request"),
},
)
@action(detail=True, pagination_class=None, filter_backends=[])

View File

View File

@ -0,0 +1,106 @@
"""authentik shell command"""
import code
import platform
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
from authentik import __version__
from authentik.core.models import User
from authentik.events.middleware import IGNORED_MODELS
from authentik.events.models import Event, EventAction
from authentik.events.utils import model_to_dict
BANNER_TEXT = """### authentik shell ({authentik})
### Node {node} | Arch {arch} | Python {python} """.format(
node=platform.node(),
python=platform.python_version(),
arch=platform.machine(),
authentik=__version__,
)
class Command(BaseCommand): # pragma: no cover
"""Start the Django shell with all authentik models already imported"""
django_models = {}
def add_arguments(self, parser):
parser.add_argument(
"-c",
"--command",
help="Python code to execute (instead of starting an interactive shell)",
)
def get_namespace(self):
"""Prepare namespace with all models"""
namespace = {}
# Gather Django models and constants from each app
for app in apps.get_app_configs():
if not app.name.startswith("authentik"):
continue
# Load models from each app
for model in app.get_models():
namespace[model.__name__] = model
return namespace
@staticmethod
# pylint: disable=unused-argument
def post_save_handler(sender, instance: Model, created: bool, **_):
"""Signal handler for all object's post_save"""
if isinstance(instance, IGNORED_MODELS):
return
action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED
Event.new(action, model=model_to_dict(instance)).set_user(
User(
username="authentik-shell",
pk=0,
email="",
)
).save()
@staticmethod
# pylint: disable=unused-argument
def pre_delete_handler(sender, instance: Model, **_):
"""Signal handler for all object's pre_delete"""
if isinstance(instance, IGNORED_MODELS): # pragma: no cover
return
Event.new(EventAction.MODEL_DELETED, model=model_to_dict(instance)).set_user(
User(
username="authentik-shell",
pk=0,
email="",
)
).save()
def handle(self, **options):
namespace = self.get_namespace()
post_save.connect(Command.post_save_handler)
pre_delete.connect(Command.pre_delete_handler)
# If Python code has been passed, execute it and exit.
if options["command"]:
# pylint: disable=exec-used
exec(options["command"], namespace) # nosec # noqa
return
# Try to enable tab-complete
try:
import readline
import rlcompleter
except ModuleNotFoundError:
pass
else:
readline.set_completer(rlcompleter.Completer(namespace).complete)
readline.parse_and_bind("tab: complete")
# Run interactive shell
code.interact(banner=BANNER_TEXT, local=namespace)

View File

@ -34,9 +34,9 @@ def clean_expired_models(self: MonitoredTask):
objects = (
cls.objects.all().exclude(expiring=False).exclude(expiring=True, expires__gt=now())
)
amount = objects.count()
for obj in objects:
obj.expire_action()
amount = objects.count()
LOGGER.debug("Expired models", model=cls, amount=amount)
messages.append(f"Expired {amount} {cls._meta.verbose_name_plural}")
# Special case

View File

@ -8,6 +8,12 @@
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
<script>
window.authentik = {};
window.authentik.flow = {
"layout": "{{ flow.layout }}",
};
</script>
{% endblock %}
{% block head %}

View File

@ -12,6 +12,25 @@
.pf-c-background-image::before {
--ak-flow-background: url("/static/dist/assets/images/flow_background.jpg");
}
/* Form with user */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-static .avatar {
display: flex;
align-items: center;
}
.form-control-static img {
margin-right: var(--pf-global--spacer--xs);
}
.form-control-static a {
padding-top: var(--pf-global--spacer--xs);
padding-bottom: var(--pf-global--spacer--xs);
line-height: var(--pf-global--spacer--xl);
}
</style>
{% endblock %}
@ -59,13 +78,11 @@
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
{% if tenant.branding_title != "authentik" %}
<li>
<a href="https://goauthentik.io?utm_source=authentik">
{% trans 'Powered by authentik' %}
</a>
</li>
{% endif %}
</ul>
</footer>
</div>

View File

@ -1,4 +1,5 @@
"""authentik URL Configuration"""
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.views.decorators.csrf import ensure_csrf_cookie
@ -6,6 +7,7 @@ from django.views.generic import RedirectView
from django.views.generic.base import TemplateView
from authentik.core.views import apps, impersonate
from authentik.core.views.debug import AccessDeniedView
from authentik.core.views.interface import FlowInterfaceView
from authentik.core.views.session import EndSessionView
@ -60,3 +62,8 @@ urlpatterns = [
TemplateView.as_view(template_name="if/admin.html"),
),
]
if settings.DEBUG:
urlpatterns += [
path("debug/policy/deny/", AccessDeniedView.as_view(), name="debug-policy-deny"),
]

View File

@ -0,0 +1,12 @@
"""debug view"""
from django.http import HttpRequest, HttpResponse
from django.views.generic import View
from authentik.policies.denied import AccessDeniedResponse
class AccessDeniedView(View):
"""Easily access AccessDeniedResponse"""
def dispatch(self, request: HttpRequest) -> HttpResponse:
return AccessDeniedResponse(request)

View File

@ -8,6 +8,7 @@ from structlog.stdlib import get_logger
from authentik.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
LOGGER = get_logger()
@ -17,6 +18,9 @@ class ImpersonateInitView(View):
def get(self, request: HttpRequest, user_id: int) -> HttpResponse:
"""Impersonation handler, checks permissions"""
if not CONFIG.y_bool("impersonation"):
LOGGER.debug("User attempted to impersonate", user=request.user)
return HttpResponse("Unauthorized", status=401)
if not request.user.has_perm("impersonate"):
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
return HttpResponse("Unauthorized", status=401)

View File

@ -1,10 +1,12 @@
"""Crypto task Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"crypto_certificate_discovery": {
"task": "authentik.crypto.tasks.certificate_discovery",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute=fqdn_rand("crypto_certificate_discovery"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -13,7 +13,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.events.api.event import EventSerializer
from authentik.events.api.events import EventSerializer
from authentik.events.models import Notification
@ -55,6 +55,7 @@ class NotificationViewSet(
"created",
"event",
"seen",
"user",
]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]

View File

@ -18,13 +18,18 @@ from authentik.events.utils import model_to_dict
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
IGNORED_MODELS = (
IGNORED_MODELS = [
Event,
Notification,
UserObjectPermission,
AuthenticatedSession,
StaticToken,
)
]
if settings.DEBUG:
from silk.models import Request, Response
IGNORED_MODELS += [Request, Response]
IGNORED_MODELS = tuple(IGNORED_MODELS)
class AuditMiddleware:

View File

@ -261,7 +261,7 @@ class Event(ExpiringModel):
def save(self, *args, **kwargs):
if self._state.adding:
LOGGER.debug(
LOGGER.info(
"Created Event",
action=self.action,
context=self.context,
@ -518,7 +518,7 @@ class NotificationWebhookMapping(PropertyMapping):
@property
def serializer(self) -> type["Serializer"]:
from authentik.events.api.notification_mapping import NotificationWebhookMappingSerializer
from authentik.events.api.notification_mappings import NotificationWebhookMappingSerializer
return NotificationWebhookMappingSerializer

View File

@ -0,0 +1,12 @@
"""Event Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"events_notification_cleanup": {
"task": "authentik.events.tasks.notification_cleanup",
"schedule": crontab(minute=fqdn_rand("notification_cleanup"), hour="*/8"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -1,4 +1,5 @@
"""Event notification tasks"""
from django.db.models.query_utils import Q
from guardian.shortcuts import get_anonymous_user
from structlog.stdlib import get_logger
@ -10,7 +11,12 @@ from authentik.events.models import (
NotificationTransport,
NotificationTransportError,
)
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBinding, PolicyEngineMode
from authentik.root.celery import CELERY_APP
@ -114,3 +120,15 @@ def gdpr_cleanup(user_pk: int):
events = Event.objects.filter(user__pk=user_pk)
LOGGER.debug("GDPR cleanup, removing events from user", events=events.count())
events.delete()
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task
def notification_cleanup(self: MonitoredTask):
"""Cleanup seen notifications and notifications whose event expired."""
notifications = Notification.objects.filter(Q(event=None) | Q(seen=True))
amount = notifications.count()
for notification in notifications:
notification.delete()
LOGGER.debug("Expired notifications", amount=amount)
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, [f"Expired {amount} Notifications"]))

View File

@ -72,6 +72,7 @@ class FlowSerializer(ModelSerializer):
"policy_engine_mode",
"compatibility_mode",
"export_url",
"layout",
]
extra_kwargs = {
"background": {"read_only": True},
@ -211,12 +212,30 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
]
body: list[DiagramElement] = []
footer = []
# First, collect all elements we need
# Collect all elements we need
# First, policies bound to the flow itself
for p_index, policy_binding in enumerate(
get_objects_for_user(request.user, "authentik_policies.view_policybinding")
.filter(target=flow)
.exclude(policy__isnull=True)
.order_by("order")
):
body.append(
DiagramElement(
f"flow_policy_{p_index}",
"condition",
_("Policy (%(type)s)" % {"type": policy_binding.policy._meta.verbose_name})
+ "\n"
+ policy_binding.policy.name,
)
)
# Collect all stages
for s_index, stage_binding in enumerate(
get_objects_for_user(request.user, "authentik_flows.view_flowstagebinding")
.filter(target=flow)
.order_by("order")
):
# First all policies bound to stages since they execute before stages
for p_index, policy_binding in enumerate(
get_objects_for_user(request.user, "authentik_policies.view_policybinding")
.filter(target=stage_binding)
@ -227,14 +246,18 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
DiagramElement(
f"stage_{s_index}_policy_{p_index}",
"condition",
f"Policy\n{policy_binding.policy.name}",
_("Policy (%(type)s)" % {"type": policy_binding.policy._meta.verbose_name})
+ "\n"
+ policy_binding.policy.name,
)
)
body.append(
DiagramElement(
f"stage_{s_index}",
"operation",
f"Stage\n{stage_binding.stage.name}",
_("Stage (%(type)s)" % {"type": stage_binding.stage._meta.verbose_name})
+ "\n"
+ stage_binding.stage.name,
)
)
# If the 2nd last element is a policy, we need to have an item to point to

View File

@ -2,6 +2,7 @@
from enum import Enum
from typing import TYPE_CHECKING, Optional
from django.db import models
from django.http import JsonResponse
from rest_framework.fields import ChoiceField, DictField
from rest_framework.serializers import CharField
@ -12,6 +13,20 @@ from authentik.flows.transfer.common import DataclassEncoder
if TYPE_CHECKING:
from authentik.flows.stage import StageView
PLAN_CONTEXT_TITLE = "title"
PLAN_CONTEXT_URL = "url"
PLAN_CONTEXT_ATTRS = "attrs"
class FlowLayout(models.TextChoices):
"""Flow layouts"""
STACKED = "stacked"
CONTENT_LEFT = "content_left"
CONTENT_RIGHT = "content_right"
SIDEBAR_LEFT = "sidebar_left"
SIDEBAR_RIGHT = "sidebar_right"
class ChallengeTypes(Enum):
"""Currently defined challenge types"""
@ -34,6 +49,7 @@ class ContextualFlowInfo(PassiveSerializer):
title = CharField(required=False, allow_blank=True)
background = CharField(required=False)
cancel_url = CharField()
layout = ChoiceField(choices=[(x.value, x.name) for x in FlowLayout])
class Challenge(PassiveSerializer):
@ -97,6 +113,21 @@ class ChallengeResponse(PassiveSerializer):
super().__init__(instance=instance, data=data, **kwargs)
class AutosubmitChallenge(Challenge):
"""Autosubmit challenge used to send and navigate a POST request"""
url = CharField()
attrs = DictField(child=CharField())
title = CharField(required=False)
component = CharField(default="ak-stage-autosubmit")
class AutoSubmitChallengeResponse(ChallengeResponse):
"""Pseudo class for autosubmit response"""
component = CharField(default="ak-stage-autosubmit")
class HttpChallengeResponse(JsonResponse):
"""Subclass of JsonResponse that uses the `DataclassEncoder`"""

View File

@ -12,3 +12,7 @@ class FlowNonApplicableException(SentryIgnoredException):
class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages."""
class FlowSkipStageException(SentryIgnoredException):
"""Exception to skip a stage"""

View File

@ -130,7 +130,7 @@ class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0017_auto_20210329_1334"),
("authentik_stages_user_write", "0002_auto_20200918_1653"),
("authentik_stages_user_login", "__latest__"),
("authentik_stages_user_login", "0003_session_duration_delta"),
("authentik_stages_password", "0002_passwordstage_change_flow"),
("authentik_policies", "0001_initial"),
("authentik_policies_expression", "0001_initial"),

View File

@ -0,0 +1,27 @@
# Generated by Django 4.0.4 on 2022-05-15 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0021_auto_20211227_2103"),
]
operations = [
migrations.AddField(
model_name="flow",
name="layout",
field=models.TextField(
choices=[
("stacked", "Stacked"),
("content_left", "Content Left"),
("content_right", "Content Right"),
("sidebar_left", "Sidebar Left"),
("sidebar_right", "Sidebar Right"),
],
default="stacked",
),
),
]

View File

@ -13,6 +13,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Token
from authentik.core.types import UserSettingSerializer
from authentik.flows.challenge import FlowLayout
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.policies.models import PolicyBindingModel
@ -107,6 +108,7 @@ class Flow(SerializerModel, PolicyBindingModel):
slug = models.SlugField(unique=True, help_text=_("Visible in the URL."))
title = models.TextField(help_text=_("Shown as the Title in Flow pages."))
layout = models.TextField(default=FlowLayout.STACKED, choices=FlowLayout.choices)
designation = models.CharField(
max_length=100,
@ -231,7 +233,7 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
return FlowStageBindingSerializer
def __str__(self) -> str:
return f"Flow-stage binding #{self.order} to {self.target}"
return f"Flow-stage binding #{self.order} to {self.target_id}"
class Meta:

View File

@ -120,9 +120,12 @@ class ChallengeStageView(StageView):
return self.executor.flow.title
try:
return self.executor.flow.title % {
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, "")
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, ""),
"user": self.get_pending_user(for_display=True),
}
except ValueError:
# pylint: disable=broad-except
except Exception as exc:
LOGGER.warning("failed to template title", exc=exc)
return self.executor.flow.title
def _get_challenge(self, *args, **kwargs) -> Challenge:
@ -131,25 +134,32 @@ class ChallengeStageView(StageView):
description=self.__class__.__name__,
):
challenge = self.get_challenge(*args, **kwargs)
if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo(
data={
"title": self.format_title(),
"background": self.executor.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
}
)
flow_info.is_valid()
challenge.initial_data["flow_info"] = flow_info.data
if isinstance(challenge, WithUserInfoChallenge):
# If there's a pending user, update the `username` field
# this field is only used by password managers.
# If there's no user set, an error is raised later.
if user := self.get_pending_user(for_display=True):
challenge.initial_data["pending_user"] = user.username
challenge.initial_data["pending_user_avatar"] = DEFAULT_AVATAR
if not isinstance(user, AnonymousUser):
challenge.initial_data["pending_user_avatar"] = user.avatar
with Hub.current.start_span(
op="authentik.flow.stage._get_challenge",
description=self.__class__.__name__,
):
if not hasattr(challenge, "initial_data"):
challenge.initial_data = {}
if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo(
data={
"title": self.format_title(),
"background": self.executor.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"layout": self.executor.flow.layout,
}
)
flow_info.is_valid()
challenge.initial_data["flow_info"] = flow_info.data
if isinstance(challenge, WithUserInfoChallenge):
# If there's a pending user, update the `username` field
# this field is only used by password managers.
# If there's no user set, an error is raised later.
if user := self.get_pending_user(for_display=True):
challenge.initial_data["pending_user"] = user.username
challenge.initial_data["pending_user_avatar"] = DEFAULT_AVATAR
if not isinstance(user, AnonymousUser):
challenge.initial_data["pending_user_avatar"] = user.avatar
return challenge
def get_challenge(self, *args, **kwargs) -> Challenge:

View File

@ -23,6 +23,7 @@ class FlowTestCase(APITestCase):
**kwargs,
) -> dict[str, Any]:
"""Assert various attributes of a stage response"""
self.assertEqual(response.status_code, 200)
raw_response = loads(response.content.decode())
self.assertIsNotNone(raw_response["component"])
self.assertIsNotNone(raw_response["type"])

View File

@ -10,11 +10,11 @@ from authentik.policies.models import PolicyBinding
from authentik.stages.dummy.models import DummyStage
DIAGRAM_EXPECTED = """st=>start: Start
stage_0=>operation: Stage
stage_0=>operation: Stage (Dummy Stage)
dummy1
stage_1_policy_0=>condition: Policy
None
stage_1=>operation: Stage
stage_1_policy_0=>condition: Policy (Dummy Policy)
test
stage_1=>operation: Stage (Dummy Stage)
dummy2
e=>end: End|future
st(right)->stage_0
@ -55,7 +55,7 @@ class TestFlowsAPI(APITestCase):
slug="test-default-context",
designation=FlowDesignation.AUTHENTICATION,
)
false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)
false_policy = DummyPolicy.objects.create(name="test", result=False, wait_min=1, wait_max=2)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0

View File

@ -87,7 +87,6 @@ class TestFlowExecutor(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow=flow,
@ -406,7 +405,6 @@ class TestFlowExecutor(FlowTestCase):
# A get request will evaluate the policies and this will return stage 4
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
# fourth request, this confirms the last stage (dummy4)
@ -479,7 +477,6 @@ class TestFlowExecutor(FlowTestCase):
exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow,
@ -491,5 +488,4 @@ class TestFlowExecutor(FlowTestCase):
user_fields=[UserFields.E_MAIL],
)
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")

View File

@ -55,6 +55,7 @@ class TestFlowInspector(APITestCase):
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
"layout": "stacked",
},
"type": ChallengeTypes.NATIVE.value,
"password_fields": False,

View File

@ -169,10 +169,11 @@ class FlowExecutorView(APIView):
self.request.session[SESSION_KEY_PLAN] = plan
# Early check if there's an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN]
self.plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning(
"f(exec): Found existing plan for other flow, deleting plan",
other_flow=self.plan.flow_pk,
)
# Existing plan is deleted from session and instance
self.plan = None

View File

@ -72,3 +72,4 @@ default_user_change_username: true
gdpr_compliance: true
cert_discovery_dir: /certs
default_token_length: 128
impersonation: true

View File

@ -18,13 +18,22 @@ from redis.exceptions import ConnectionError as RedisConnectionError
from redis.exceptions import RedisError, ResponseError
from rest_framework.exceptions import APIException
from sentry_sdk import Hub
from sentry_sdk import init as sentry_sdk_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.threading import ThreadingIntegration
from sentry_sdk.tracing import Transaction
from structlog.stdlib import get_logger
from websockets.exceptions import WebSocketException
from authentik.lib.utils.reflection import class_to_path
from authentik import __version__, get_build_hash
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import class_to_path, get_env
LOGGER = get_logger()
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
class SentryWSMiddleware(BaseMiddleware):
@ -43,6 +52,37 @@ class SentryIgnoredException(Exception):
"""Base Class for all errors that are suppressed, and not sent to sentry."""
def sentry_init(**sentry_init_kwargs):
"""Configure sentry SDK"""
sentry_env = CONFIG.y("error_reporting.environment", "customer")
kwargs = {
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.5)),
"environment": sentry_env,
"send_default_pii": CONFIG.y_bool("error_reporting.send_pii", False),
}
kwargs.update(**sentry_init_kwargs)
# pylint: disable=abstract-class-instantiated
sentry_sdk_init(
dsn=SENTRY_DSN,
integrations=[
DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(),
RedisIntegration(),
ThreadingIntegration(propagate_hub=True),
],
before_send=before_send,
release=f"authentik@{__version__}",
**kwargs,
)
set_tag("authentik.build_hash", get_build_hash("tagged"))
set_tag("authentik.env", get_env())
set_tag("authentik.component", "backend")
LOGGER.info(
"Error reporting is enabled",
env=kwargs["environment"],
)
def before_send(event: dict, hint: dict) -> Optional[dict]:
"""Check if error is database error, and ignore if so"""
# pylint: disable=no-name-in-module
@ -108,6 +148,6 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
]:
return None
LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None))
if settings.DEBUG or settings.TEST:
if settings.DEBUG:
return None
return event

View File

@ -13,4 +13,4 @@ class TestSentry(TestCase):
def test_error_sent(self):
"""Test error sent"""
self.assertEqual(None, before_send({}, {"exc_info": (0, ValueError(), 0)}))
self.assertEqual({}, before_send({}, {"exc_info": (0, ValueError(), 0)}))

View File

@ -1,5 +1,8 @@
"""Time utilities"""
import datetime
from hashlib import sha256
from random import randrange, seed
from socket import getfqdn
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
@ -38,3 +41,12 @@ def timedelta_from_string(expr: str) -> datetime.timedelta:
if len(kwargs) < 1:
raise ValueError("No valid keys to pass to timedelta")
return datetime.timedelta(**kwargs)
def fqdn_rand(task: str, stop: int = 60) -> int:
"""Get a random number within max based on the FQDN and task name"""
entropy = f"{getfqdn()}:{task}"
hasher = sha256()
hasher.update(entropy.encode("utf-8"))
seed(hasher.hexdigest())
return randrange(0, stop) # nosec

View File

@ -1,7 +1,8 @@
"""URL-related utils"""
from typing import Optional
from urllib.parse import urlparse
from django.http import HttpResponse
from django.http import HttpResponse, QueryDict
from django.shortcuts import redirect
from django.urls import NoReverseMatch, reverse
from django.utils.http import urlencode
@ -15,7 +16,9 @@ def is_url_absolute(url):
return bool(urlparse(url).netloc)
def redirect_with_qs(view: str, get_query_set=None, **kwargs) -> HttpResponse:
def redirect_with_qs(
view: str, get_query_set: Optional[QueryDict] = None, **kwargs
) -> HttpResponse:
"""Wrapper to redirect whilst keeping GET Parameters"""
try:
target = reverse(view, kwargs=kwargs)
@ -28,3 +31,11 @@ def redirect_with_qs(view: str, get_query_set=None, **kwargs) -> HttpResponse:
if get_query_set:
target += "?" + urlencode(get_query_set.items())
return redirect(target)
def reverse_with_qs(view: str, query: Optional[QueryDict] = None, **kwargs) -> str:
"""Reverse a view to it's url but include get params"""
url = reverse(view, **kwargs)
if query:
url += "?" + urlencode(query.items())
return url

View File

@ -1,10 +1,12 @@
"""managed Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"managed_reconcile": {
"task": "authentik.managed.tasks.managed_reconcile",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute=fqdn_rand("managed_reconcile"), hour="*/4"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -1,25 +1,27 @@
"""Outposts Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"outposts_controller": {
"task": "authentik.outposts.tasks.outpost_controller_all",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute=fqdn_rand("outposts_controller"), hour="*/4"),
"options": {"queue": "authentik_scheduled"},
},
"outposts_service_connection_check": {
"task": "authentik.outposts.tasks.outpost_service_connection_monitor",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute="3-59/15"),
"options": {"queue": "authentik_scheduled"},
},
"outpost_token_ensurer": {
"task": "authentik.outposts.tasks.outpost_token_ensurer",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute=fqdn_rand("outpost_token_ensurer"), hour="*/8"),
"options": {"queue": "authentik_scheduled"},
},
"outpost_local_connection": {
"task": "authentik.outposts.tasks.outpost_local_connection",
"schedule": crontab(minute="*/60"),
"schedule": crontab(minute=fqdn_rand("outpost_local_connection"), hour="*/8"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -3,6 +3,7 @@ from typing import Any, Optional
from django.http.request import HttpRequest
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.translation import gettext as _
from authentik.core.models import USER_ATTRIBUTE_DEBUG
@ -37,4 +38,5 @@ class AccessDeniedResponse(TemplateResponse):
self._request
).get(USER_ATTRIBUTE_DEBUG, False):
context["policy_result"] = self.policy_result
context["cancel"] = reverse("authentik_flows:cancel")
return context

View File

@ -9,6 +9,7 @@ from structlog.stdlib import get_logger
from authentik.lib.utils.http import get_http_session
from authentik.policies.models import Policy, PolicyResult
from authentik.policies.types import PolicyRequest
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
@ -38,14 +39,17 @@ class HaveIBeenPwendPolicy(Policy):
"""Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5
characters of Password in request and checks if full hash is in response. Returns 0
if Password is not in result otherwise the count of how many times it was used."""
if self.password_field not in request.context:
password = request.context.get(PLAN_CONTEXT_PROMPT, {}).get(
self.password_field, request.context.get(self.password_field)
)
if not password:
LOGGER.warning(
"Password field not set in Policy Request",
field=self.password_field,
fields=request.context.keys(),
)
return PolicyResult(False, _("Password not set in context"))
password = str(request.context[self.password_field])
password = str(password)
pw_hash = sha1(password.encode("utf-8")).hexdigest() # nosec
url = f"https://api.pwnedpasswords.com/range/{pw_hash[:5]}"

View File

@ -5,6 +5,7 @@ from guardian.shortcuts import get_anonymous_user
from authentik.lib.generators import generate_key
from authentik.policies.hibp.models import HaveIBeenPwendPolicy
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
class TestHIBPPolicy(TestCase):
@ -26,7 +27,7 @@ class TestHIBPPolicy(TestCase):
name="test_false",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "password" # nosec
request.context[PLAN_CONTEXT_PROMPT] = {"password": "password"} # nosec
result: PolicyResult = policy.passes(request)
self.assertFalse(result.passing)
self.assertTrue(result.messages[0].startswith("Password exists on "))
@ -37,7 +38,7 @@ class TestHIBPPolicy(TestCase):
name="test_true",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = generate_key()
request.context[PLAN_CONTEXT_PROMPT] = {"password": generate_key()}
result: PolicyResult = policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -50,7 +50,6 @@ class TestPasswordPolicyFlow(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
{"password": "akadmin"},
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -4,7 +4,7 @@ from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"policies_reputation_save": {
"task": "authentik.policies.reputation.tasks.save_reputation",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute="1-59/5"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -12,8 +12,21 @@
{% endblock %}
{% block card %}
<form method="POST" class="pf-c-form">
<form class="pf-c-form">
{% csrf_token %}
{% if user.is_authenticated %}
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="avatar">
<img class="pf-c-avatar" src="{{ user.avatar }}" alt="{% trans "User's avatar" %}" />
{{ user.username }}
</div>
<div slot="link">
<a href="{{ cancel }}">{% trans "Not you?" %}</a>
</div>
</div>
</div>
{% endif %}
<div class="pf-c-form__group">
<p>
<i class="pf-icon pf-icon-error-circle-o"></i>

View File

@ -25,6 +25,7 @@ class LDAPProviderSerializer(ProviderSerializer):
"gid_start_number",
"outpost_set",
"search_mode",
"bind_mode",
]
@ -70,6 +71,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"uid_start_number",
"gid_start_number",
"search_mode",
"bind_mode",
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.0.4 on 2022-05-08 13:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0001_squashed_0005_ldapprovider_search_mode"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="bind_mode",
field=models.TextField(
choices=[("direct", "Direct"), ("cached", "Cached")], default="direct"
),
),
]

View File

@ -10,8 +10,8 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.models import OutpostModel
class SearchModes(models.TextChoices):
"""Search modes"""
class APIAccessMode(models.TextChoices):
"""API Access modes"""
DIRECT = "direct"
CACHED = "cached"
@ -66,7 +66,8 @@ class LDAPProvider(OutpostModel, Provider):
),
)
search_mode = models.TextField(default=SearchModes.DIRECT, choices=SearchModes.choices)
bind_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices)
search_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices)
@property
def launch_url(self) -> Optional[str]:

View File

@ -11,7 +11,7 @@ CLIENT_ASSERTION = "client_assertion"
CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
PROMPT_NONE = "none"
PROMPT_CONSNET = "consent"
PROMPT_CONSENT = "consent"
PROMPT_LOGIN = "login"
SCOPE_OPENID = "openid"

View File

@ -24,7 +24,7 @@ class OAuth2Error(SentryIgnoredException):
return self.error
def to_event(self, message: Optional[str] = None, **kwargs) -> Event:
"""Create configuration_error Event and save it."""
"""Create configuration_error Event."""
return Event.new(
EventAction.CONFIGURATION_ERROR,
message=message or self.description,

View File

@ -50,6 +50,7 @@ class ResponseMode(models.TextChoices):
QUERY = "query"
FRAGMENT = "fragment"
FORM_POST = "form_post"
class SubModes(models.TextChoices):

View File

@ -5,7 +5,6 @@ from django.urls import reverse
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError
from authentik.providers.oauth2.models import (
@ -178,7 +177,7 @@ class TestAuthorize(OAuthTestCase):
def test_full_code(self):
"""Test full authorization"""
flow = Flow.objects.create(slug="empty")
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
@ -214,7 +213,7 @@ class TestAuthorize(OAuthTestCase):
def test_full_implicit(self):
"""Test full authorization"""
flow = Flow.objects.create(slug="empty")
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
@ -255,3 +254,52 @@ class TestAuthorize(OAuthTestCase):
},
)
self.validate_jwt(token, provider)
def test_full_form_post(self):
"""Test full authorization (form_post response)"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
client_secret=generate_key(),
authorization_flow=flow,
redirect_uris="http://localhost",
signing_key=create_test_cert(),
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
"response_mode": "form_post",
"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()
self.assertJSONEqual(
response.content.decode(),
{
"component": "ak-stage-autosubmit",
"type": ChallengeTypes.NATIVE.value,
"url": "http://localhost",
"title": "Redirecting to app...",
"attrs": {
"access_token": token.access_token,
"id_token": provider.encode(token.id_token.to_dict()),
"token_type": "bearer",
"expires_in": "60",
"state": state,
},
},
)
self.validate_jwt(token, provider)

View File

@ -1,6 +1,7 @@
"""authentik OAuth2 Authorization views"""
from dataclasses import dataclass, field
from datetime import timedelta
from re import fullmatch
from typing import Optional
from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
from uuid import uuid4
@ -15,6 +16,12 @@ from structlog.stdlib import get_logger
from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.flows.challenge import (
PLAN_CONTEXT_TITLE,
AutosubmitChallenge,
ChallengeTypes,
HttpChallengeResponse,
)
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import (
PLAN_CONTEXT_APPLICATION,
@ -30,7 +37,7 @@ from authentik.lib.views import bad_request_message
from authentik.policies.types import PolicyRequest
from authentik.policies.views import PolicyAccessView, RequestValidationError
from authentik.providers.oauth2.constants import (
PROMPT_CONSNET,
PROMPT_CONSENT,
PROMPT_LOGIN,
PROMPT_NONE,
SCOPE_OPENID,
@ -63,7 +70,7 @@ LOGGER = get_logger()
PLAN_CONTEXT_PARAMS = "params"
SESSION_NEEDS_LOGIN = "authentik_oauth2_needs_login"
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSNET, PROMPT_LOGIN}
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSENT, PROMPT_LOGIN}
@dataclass
@ -74,6 +81,7 @@ class OAuthAuthorizationParams:
client_id: str
redirect_uri: str
response_type: str
response_mode: Optional[str]
scope: list[str]
state: str
nonce: Optional[str]
@ -125,11 +133,22 @@ class OAuthAuthorizationParams:
LOGGER.warning("Invalid response type", type=response_type)
raise AuthorizeError(redirect_uri, "unsupported_response_type", "", state)
# Validate and check the response_mode against the predefined dict
# Set to Query or Fragment if not defined in request
response_mode = query_dict.get("response_mode", False)
if response_mode not in ResponseMode.values:
response_mode = ResponseMode.QUERY
if grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
response_mode = ResponseMode.FRAGMENT
max_age = query_dict.get("max_age")
return OAuthAuthorizationParams(
client_id=query_dict.get("client_id", ""),
redirect_uri=redirect_uri,
response_type=response_type,
response_mode=response_mode,
grant_type=grant_type,
scope=query_dict.get("scope", "").split(),
state=state,
@ -155,10 +174,7 @@ class OAuthAuthorizationParams:
def check_redirect_uri(self):
"""Redirect URI validation."""
allowed_redirect_urls = self.provider.redirect_uris.split()
# We don't want to actually lowercase the final URL we redirect to,
# we only lowercase it for comparison
redirect_uri = self.redirect_uri.lower()
if not redirect_uri:
if not self.redirect_uri:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", allowed_redirect_urls)
@ -168,13 +184,7 @@ class OAuthAuthorizationParams:
self.provider.save()
allowed_redirect_urls = self.provider.redirect_uris.split()
if self.provider.redirect_uris == "*":
LOGGER.warning(
"Provider has wildcard allowed redirect_uri set, allowing all.",
allow=self.redirect_uri,
)
return
if redirect_uri not in [x.lower() for x in allowed_redirect_urls]:
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri",
redirect_uri=self.redirect_uri,
@ -239,167 +249,6 @@ class OAuthAuthorizationParams:
return code
class OAuthFulfillmentStage(StageView):
"""Final stage, restores params from Flow."""
params: OAuthAuthorizationParams
provider: OAuth2Provider
def redirect(self, uri: str) -> HttpResponse:
"""Redirect using HttpResponseRedirectScheme, compatible with non-http schemes"""
parsed = urlparse(uri)
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Wrapper when this stage gets hit with a post request"""
return self.get(request, *args, **kwargs)
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""final Stage of an OAuth2 Flow"""
if PLAN_CONTEXT_PARAMS not in self.executor.plan.context:
LOGGER.warning("Got to fulfillment stage with no pending context")
return HttpResponseBadRequest()
self.params: OAuthAuthorizationParams = self.executor.plan.context.pop(PLAN_CONTEXT_PARAMS)
application: Application = self.executor.plan.context.pop(PLAN_CONTEXT_APPLICATION)
self.provider = get_object_or_404(OAuth2Provider, pk=application.provider_id)
try:
# At this point we don't need to check permissions anymore
if {PROMPT_NONE, PROMPT_CONSNET}.issubset(self.params.prompt):
raise AuthorizeError(
self.params.redirect_uri,
"consent_required",
self.params.grant_type,
self.params.state,
)
Event.new(
EventAction.AUTHORIZE_APPLICATION,
authorized_application=application,
flow=self.executor.plan.flow_pk,
scopes=", ".join(self.params.scope),
).from_http(self.request)
return self.redirect(self.create_response_uri())
except (ClientIdError, RedirectUriError) as error:
error.to_event(application=application).from_http(request)
self.executor.stage_invalid()
# pylint: disable=no-member
return bad_request_message(request, error.description, title=error.error)
except AuthorizeError as error:
error.to_event(application=application).from_http(request)
self.executor.stage_invalid()
return self.redirect(error.create_uri())
def create_response_uri(self) -> str:
"""Create a final Response URI the user is redirected to."""
uri = urlsplit(self.params.redirect_uri)
query_params = parse_qs(uri.query)
try:
code = None
if self.params.grant_type in [
GrantTypes.AUTHORIZATION_CODE,
GrantTypes.HYBRID,
]:
code = self.params.create_code(self.request)
code.save(force_insert=True)
query_dict = self.request.POST if self.request.method == "POST" else self.request.GET
response_mode = ResponseMode.QUERY
# Get response mode from url param, otherwise decide based on grant type
if "response_mode" in query_dict:
response_mode = query_dict["response_mode"]
elif self.params.grant_type == GrantTypes.AUTHORIZATION_CODE:
response_mode = ResponseMode.QUERY
elif self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
response_mode = ResponseMode.FRAGMENT
if response_mode == ResponseMode.QUERY:
query_params["code"] = code.code
query_params["state"] = [str(self.params.state) if self.params.state else ""]
uri = uri._replace(query=urlencode(query_params, doseq=True))
return urlunsplit(uri)
if response_mode == ResponseMode.FRAGMENT:
query_fragment = self.create_implicit_response(code)
uri = uri._replace(
fragment=uri.fragment + urlencode(query_fragment, doseq=True),
)
return urlunsplit(uri)
raise OAuth2Error()
except OAuth2Error as error:
LOGGER.warning("Error when trying to create response uri", error=error)
raise AuthorizeError(
self.params.redirect_uri,
"server_error",
self.params.grant_type,
self.params.state,
)
def create_implicit_response(self, code: Optional[AuthorizationCode]) -> dict:
"""Create implicit response's URL Fragment dictionary"""
query_fragment = {}
token = self.provider.create_refresh_token(
user=self.request.user,
scope=self.params.scope,
request=self.request,
)
# Check if response_type must include access_token in the response.
if self.params.response_type in [
ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
ResponseTypes.ID_TOKEN,
ResponseTypes.CODE_TOKEN,
]:
query_fragment["access_token"] = token.access_token
# We don't need id_token if it's an OAuth2 request.
if SCOPE_OPENID in self.params.scope:
id_token = token.create_id_token(
user=self.request.user,
request=self.request,
)
id_token.nonce = self.params.nonce
# Include at_hash when access_token is being returned.
if "access_token" in query_fragment:
id_token.at_hash = token.at_hash
if self.params.response_type in [
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
id_token.c_hash = code.c_hash
# Check if response_type must include id_token in the response.
if self.params.response_type in [
ResponseTypes.ID_TOKEN,
ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
query_fragment["id_token"] = self.provider.encode(id_token.to_dict())
token.id_token = id_token
# Store the token.
token.save()
# Code parameter must be present if it's Hybrid Flow.
if self.params.grant_type == GrantTypes.HYBRID:
query_fragment["code"] = code.code
query_fragment["token_type"] = "bearer" # nosec
query_fragment["expires_in"] = int(
timedelta_from_string(self.provider.access_code_validity).total_seconds()
)
query_fragment["state"] = self.params.state if self.params.state else ""
return query_fragment
class AuthorizationFlowInitView(PolicyAccessView):
"""OAuth2 Flow initializer, checks access to application and starts flow"""
@ -414,10 +263,10 @@ class AuthorizationFlowInitView(PolicyAccessView):
try:
self.params = OAuthAuthorizationParams.from_request(self.request)
except AuthorizeError as error:
error.to_event(redirect_uri=error.redirect_uri).from_http(self.request)
LOGGER.warning(error.description, redirect_uri=error.redirect_uri)
raise RequestValidationError(HttpResponseRedirect(error.create_uri()))
except OAuth2Error as error:
error.to_event().from_http(self.request)
LOGGER.warning(error.description)
raise RequestValidationError(
bad_request_message(self.request, error.description, title=error.error)
)
@ -494,7 +343,7 @@ class AuthorizationFlowInitView(PolicyAccessView):
)
# OpenID clients can specify a `prompt` parameter, and if its set to consent we
# need to inject a consent stage
if PROMPT_CONSNET in self.params.prompt:
if PROMPT_CONSENT in self.params.prompt:
if not any(isinstance(x.stage, ConsentStageView) for x in plan.bindings):
# Plan does not have any consent stage, so we add an in-memory one
stage = ConsentStage(
@ -502,10 +351,206 @@ class AuthorizationFlowInitView(PolicyAccessView):
mode=ConsentMode.ALWAYS_REQUIRE,
)
plan.append_stage(stage)
plan.append_stage(in_memory_stage(OAuthFulfillmentStage))
self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=self.provider.authorization_flow.slug,
)
class OAuthFulfillmentStage(StageView):
"""Final stage, restores params from Flow."""
params: OAuthAuthorizationParams
provider: OAuth2Provider
application: Application
def redirect(self, uri: str) -> HttpResponse:
"""Redirect using HttpResponseRedirectScheme, compatible with non-http schemes"""
parsed = urlparse(uri)
if self.params.response_mode == ResponseMode.FORM_POST:
# parse_qs returns a dictionary with values wrapped in lists, however
# we need a flat dictionary for the autosubmit challenge
# this picks the first item in the list if the value is a list,
# otherwise just the value as-is
query_params = dict(
(k, v[0] if isinstance(v, list) else v) for k, v in parse_qs(parsed.query).items()
)
challenge = AutosubmitChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-autosubmit",
"title": (
self.executor.plan.context.get(
PLAN_CONTEXT_TITLE,
_("Redirecting to %(app)s..." % {"app": self.application.name}),
)
),
"url": self.params.redirect_uri,
"attrs": query_params,
}
)
challenge.is_valid()
return HttpChallengeResponse(
challenge=challenge,
)
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Wrapper when this stage gets hit with a post request"""
return self.get(request, *args, **kwargs)
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""final Stage of an OAuth2 Flow"""
if PLAN_CONTEXT_PARAMS not in self.executor.plan.context:
LOGGER.warning("Got to fulfillment stage with no pending context")
return HttpResponseBadRequest()
self.params: OAuthAuthorizationParams = self.executor.plan.context.pop(PLAN_CONTEXT_PARAMS)
self.application: Application = self.executor.plan.context.pop(PLAN_CONTEXT_APPLICATION)
self.provider = get_object_or_404(OAuth2Provider, pk=self.application.provider_id)
try:
# At this point we don't need to check permissions anymore
if {PROMPT_NONE, PROMPT_CONSENT}.issubset(self.params.prompt):
raise AuthorizeError(
self.params.redirect_uri,
"consent_required",
self.params.grant_type,
self.params.state,
)
Event.new(
EventAction.AUTHORIZE_APPLICATION,
authorized_application=self.application,
flow=self.executor.plan.flow_pk,
scopes=", ".join(self.params.scope),
).from_http(self.request)
return self.redirect(self.create_response_uri())
except (ClientIdError, RedirectUriError) as error:
error.to_event(application=self.application).from_http(request)
self.executor.stage_invalid()
# pylint: disable=no-member
return bad_request_message(request, error.description, title=error.error)
except AuthorizeError as error:
error.to_event(application=self.application).from_http(request)
self.executor.stage_invalid()
return self.redirect(error.create_uri())
def create_response_uri(self) -> str:
"""Create a final Response URI the user is redirected to."""
uri = urlsplit(self.params.redirect_uri)
query_params = parse_qs(uri.query)
try:
code = None
if self.params.grant_type in [
GrantTypes.AUTHORIZATION_CODE,
GrantTypes.HYBRID,
]:
code = self.params.create_code(self.request)
code.save(force_insert=True)
if self.params.response_mode == ResponseMode.QUERY:
query_params["code"] = code.code
query_params["state"] = [str(self.params.state) if self.params.state else ""]
uri = uri._replace(query=urlencode(query_params, doseq=True))
return urlunsplit(uri)
if self.params.response_mode == ResponseMode.FRAGMENT:
query_fragment = self.create_implicit_response(code)
uri = uri._replace(
fragment=uri.fragment + urlencode(query_fragment, doseq=True),
)
return urlunsplit(uri)
if self.params.response_mode == ResponseMode.FORM_POST:
post_params = self.create_implicit_response(code)
uri = uri._replace(query=urlencode(post_params, doseq=True))
return urlunsplit(uri)
raise OAuth2Error()
except OAuth2Error as error:
LOGGER.warning("Error when trying to create response uri", error=error)
raise AuthorizeError(
self.params.redirect_uri,
"server_error",
self.params.grant_type,
self.params.state,
)
def create_implicit_response(self, code: Optional[AuthorizationCode]) -> dict:
"""Create implicit response's URL Fragment dictionary"""
query_fragment = {}
token = self.provider.create_refresh_token(
user=self.request.user,
scope=self.params.scope,
request=self.request,
)
# Check if response_type must include access_token in the response.
if self.params.response_type in [
ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
ResponseTypes.ID_TOKEN,
ResponseTypes.CODE_TOKEN,
]:
query_fragment["access_token"] = token.access_token
# We don't need id_token if it's an OAuth2 request.
if SCOPE_OPENID in self.params.scope:
id_token = token.create_id_token(
user=self.request.user,
request=self.request,
)
id_token.nonce = self.params.nonce
# Include at_hash when access_token is being returned.
if "access_token" in query_fragment:
id_token.at_hash = token.at_hash
if self.params.response_type in [
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
id_token.c_hash = code.c_hash
# Check if response_type must include id_token in the response.
if self.params.response_type in [
ResponseTypes.ID_TOKEN,
ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
query_fragment["id_token"] = self.provider.encode(id_token.to_dict())
token.id_token = id_token
# Store the token.
token.save()
# Code parameter must be present if it's Hybrid Flow.
if self.params.grant_type == GrantTypes.HYBRID:
query_fragment["code"] = code.code
query_fragment["token_type"] = "bearer" # nosec
query_fragment["expires_in"] = int(
timedelta_from_string(self.provider.access_code_validity).total_seconds()
)
query_fragment["state"] = self.params.state if self.params.state else ""
return query_fragment

View File

@ -2,12 +2,14 @@
from base64 import urlsafe_b64encode
from dataclasses import InitVar, dataclass
from hashlib import sha256
from re import fullmatch
from typing import Any, Optional
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import datetime, now
from django.views import View
from jwt import InvalidTokenError, decode
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik.core.models import (
@ -94,16 +96,19 @@ class TokenParams:
)
def __check_policy_access(self, app: Application, request: HttpRequest, **kwargs):
engine = PolicyEngine(app, self.user, request)
engine.request.context["oauth_scopes"] = self.scope
engine.request.context["oauth_grant_type"] = self.grant_type
engine.request.context["oauth_code_verifier"] = self.code_verifier
engine.request.context.update(kwargs)
engine.build()
result = engine.result
if not result.passing:
LOGGER.info("User not authenticated for application", user=self.user, app=app)
raise TokenError("invalid_grant")
with Hub.current.start_span(
op="authentik.providers.oauth2.token.policy",
):
engine = PolicyEngine(app, self.user, request)
engine.request.context["oauth_scopes"] = self.scope
engine.request.context["oauth_grant_type"] = self.grant_type
engine.request.context["oauth_code_verifier"] = self.code_verifier
engine.request.context.update(kwargs)
engine.build()
result = engine.result
if not result.passing:
LOGGER.info("User not authenticated for application", user=self.user, app=app)
raise TokenError("invalid_grant")
def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest):
if self.grant_type in [GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN]:
@ -118,11 +123,20 @@ class TokenParams:
raise TokenError("invalid_client")
if self.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
self.__post_init_code(raw_code)
with Hub.current.start_span(
op="authentik.providers.oauth2.post.parse.code",
):
self.__post_init_code(raw_code)
elif self.grant_type == GRANT_TYPE_REFRESH_TOKEN:
self.__post_init_refresh(raw_token, request)
with Hub.current.start_span(
op="authentik.providers.oauth2.post.parse.refresh",
):
self.__post_init_refresh(raw_token, request)
elif self.grant_type in [GRANT_TYPE_CLIENT_CREDENTIALS, GRANT_TYPE_PASSWORD]:
self.__post_init_client_credentials(request)
with Hub.current.start_span(
op="authentik.providers.oauth2.post.parse.client_credentials",
):
self.__post_init_client_credentials(request)
else:
LOGGER.warning("Invalid grant type", grant_type=self.grant_type)
raise TokenError("unsupported_grant_type")
@ -133,18 +147,13 @@ class TokenParams:
raise TokenError("invalid_grant")
allowed_redirect_urls = self.provider.redirect_uris.split()
if self.provider.redirect_uris == "*":
LOGGER.warning(
"Provider has wildcard allowed redirect_uri set, allowing all.",
redirect=self.redirect_uri,
)
# At this point, no provider should have a blank redirect_uri, in case they do
# this will check an empty array and raise an error
elif self.redirect_uri not in [x.lower() for x in allowed_redirect_urls]:
if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
LOGGER.warning(
"Invalid redirect uri",
redirect=self.redirect_uri,
expected=self.provider.redirect_uris.split(),
redirect_uri=self.redirect_uri,
excepted=allowed_redirect_urls,
)
raise TokenError("invalid_client")
@ -228,6 +237,11 @@ class TokenParams:
if not token or token.user.uid != user.uid:
raise TokenError("invalid_grant")
self.user = user
# Authorize user access
app = Application.objects.filter(provider=self.provider).first()
if not app or not app.provider:
raise TokenError("invalid_grant")
self.__check_policy_access(app, request)
Event.new(
action=EventAction.LOGIN,
@ -235,13 +249,8 @@ class TokenParams:
PLAN_CONTEXT_METHOD_ARGS={
"identifier": token.identifier,
},
PLAN_CONTEXT_APPLICATION=app,
).from_http(request, user=user)
# Authorize user access
app = Application.objects.filter(provider=self.provider).first()
if not app or not app.provider:
raise TokenError("invalid_grant")
self.__check_policy_access(app, request)
return None
def __post_init_client_credentials_jwt(self, request: HttpRequest):
@ -307,6 +316,7 @@ class TokenParams:
PLAN_CONTEXT_METHOD_ARGS={
"jwt": token,
},
PLAN_CONTEXT_APPLICATION=app,
).from_http(request, user=self.user)
@ -330,27 +340,33 @@ class TokenView(View):
def post(self, request: HttpRequest) -> HttpResponse:
"""Generate tokens for clients"""
try:
client_id, client_secret = extract_client_auth(request)
try:
self.provider = OAuth2Provider.objects.get(client_id=client_id)
except OAuth2Provider.DoesNotExist:
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
raise TokenError("invalid_client")
with Hub.current.start_span(
op="authentik.providers.oauth2.post.parse",
):
client_id, client_secret = extract_client_auth(request)
try:
self.provider = OAuth2Provider.objects.get(client_id=client_id)
except OAuth2Provider.DoesNotExist:
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
raise TokenError("invalid_client")
if not self.provider:
raise ValueError
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
if not self.provider:
raise ValueError
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
LOGGER.debug("Converting authorization code to refresh token")
return TokenResponse(self.create_code_response())
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
LOGGER.debug("Refreshing refresh token")
return TokenResponse(self.create_refresh_response())
if self.params.grant_type == GRANT_TYPE_CLIENT_CREDENTIALS:
LOGGER.debug("Client credentials grant")
return TokenResponse(self.create_client_credentials_response())
raise ValueError(f"Invalid grant_type: {self.params.grant_type}")
with Hub.current.start_span(
op="authentik.providers.oauth2.post.response",
):
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
LOGGER.debug("Converting authorization code to refresh token")
return TokenResponse(self.create_code_response())
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
LOGGER.debug("Refreshing refresh token")
return TokenResponse(self.create_refresh_response())
if self.params.grant_type == GRANT_TYPE_CLIENT_CREDENTIALS:
LOGGER.debug("Client credentials grant")
return TokenResponse(self.create_client_credentials_response())
raise ValueError(f"Invalid grant_type: {self.params.grant_type}")
except TokenError as error:
return TokenResponse(error.create_dict(), status=400)
except UserAuthError as error:

View File

@ -1,4 +1,5 @@
"""SAML Identity Provider Metadata Processor"""
from hashlib import sha256
from typing import Iterator, Optional
import xmlsec # nosec
@ -7,7 +8,6 @@ from django.urls import reverse
from lxml.etree import Element, SubElement, tostring # nosec
from authentik.providers.saml.models import SAMLProvider
from authentik.providers.saml.utils import get_random_id
from authentik.providers.saml.utils.encoding import strip_pem_header
from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
@ -35,7 +35,7 @@ class MetadataProcessor:
self.provider = provider
self.http_request = request
self.force_binding = None
self.xml_id = get_random_id()
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"""

View File

@ -1,10 +1,12 @@
"""Test Service-Provider Metadata Parser"""
# flake8: noqa
from django.test import TestCase
from django.test import RequestFactory, TestCase
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert, create_test_flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.metadata import MetadataProcessor
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
METADATA_SIMPLE = """<?xml version="1.0"?>
@ -66,6 +68,23 @@ class TestServiceProviderMetadataParser(TestCase):
def setUp(self) -> None:
self.flow = create_test_flow()
self.factory = RequestFactory()
def test_consistent(self):
"""Test that metadata generation is consistent"""
provider = SAMLProvider.objects.create(
name="test",
authorization_flow=self.flow,
)
Application.objects.create(
name="test",
slug="test",
provider=provider,
)
request = self.factory.get("/")
metadata_a = MetadataProcessor(provider, request).build_entity_descriptor()
metadata_b = MetadataProcessor(provider, request).build_entity_descriptor()
self.assertEqual(metadata_a, metadata_b)
def test_simple(self):
"""Test simple metadata without Signing"""

View File

@ -4,12 +4,19 @@ from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect
from django.utils.http import urlencode
from rest_framework.fields import CharField, DictField
from django.utils.translation import gettext as _
from structlog.stdlib import get_logger
from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.challenge import (
PLAN_CONTEXT_TITLE,
AutosubmitChallenge,
AutoSubmitChallengeResponse,
Challenge,
ChallengeResponse,
ChallengeTypes,
)
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION
from authentik.flows.stage import ChallengeStageView
from authentik.lib.views import bad_request_message
@ -29,21 +36,6 @@ REQUEST_KEY_RELAY_STATE = "RelayState"
SESSION_KEY_AUTH_N_REQUEST = "authn_request"
class AutosubmitChallenge(Challenge):
"""Autosubmit challenge used to send and navigate a POST request"""
url = CharField()
attrs = DictField(child=CharField())
component = CharField(default="ak-stage-autosubmit")
class AutoSubmitChallengeResponse(ChallengeResponse):
"""Pseudo class for autosubmit response"""
component = CharField(default="ak-stage-autosubmit")
# This View doesn't have a URL on purpose, as its called by the FlowExecutor
class SAMLFlowFinalView(ChallengeStageView):
"""View used by FlowExecutor after all stages have passed. Logs the authorization,
@ -88,7 +80,12 @@ class SAMLFlowFinalView(ChallengeStageView):
**{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-autosubmit",
"title": "Redirecting to %(app)s..." % {"app": application.name},
"title": (
self.executor.plan.context.get(
PLAN_CONTEXT_TITLE,
_("Redirecting to %(app)s..." % {"app": application.name}),
)
),
"url": provider.acs_url,
"attrs": form_attrs,
},

View File

@ -11,18 +11,13 @@ from urllib.parse import quote_plus
import structlog
from celery.schedules import crontab
from sentry_sdk import init as sentry_init
from sentry_sdk.api import set_tag
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.threading import ThreadingIntegration
from sentry_sdk import set_tag
from authentik import ENV_GIT_HASH_KEY, __version__, get_build_hash
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.middleware import structlog_add_request_id
from authentik.lib.config import CONFIG
from authentik.lib.logging import add_process_id
from authentik.lib.sentry import before_send
from authentik.lib.sentry import sentry_init
from authentik.lib.utils.reflection import get_env
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
@ -60,11 +55,10 @@ LOGIN_URL = "authentik_flows:default-authentication"
# Custom user model
AUTH_USER_MODEL = "authentik_core.User"
_cookie_suffix = "_debug" if DEBUG else ""
CSRF_COOKIE_NAME = "authentik_csrf"
CSRF_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF"
LANGUAGE_COOKIE_NAME = f"authentik_language{_cookie_suffix}"
SESSION_COOKIE_NAME = f"authentik_session{_cookie_suffix}"
LANGUAGE_COOKIE_NAME = "authentik_language"
SESSION_COOKIE_NAME = "authentik_session"
SESSION_COOKIE_DOMAIN = CONFIG.y("cookie_domain", None)
AUTHENTICATION_BACKENDS = [
@ -167,6 +161,7 @@ SPECTACULAR_SETTINGS = {
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
"LDAPAPIAccessMode": "authentik.providers.ldap.models.APIAccessMode",
},
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
"POSTPROCESSING_HOOKS": [
@ -227,6 +222,7 @@ SESSION_CACHE_ALIAS = "default"
# SESSION_COOKIE_SAMESITE = "None"
# SESSION_COOKIE_SECURE = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_SAVE_EVERY_REQUEST = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
@ -337,17 +333,12 @@ CELERY_WORKER_MAX_TASKS_PER_CHILD = 50
CELERY_BEAT_SCHEDULE = {
"clean_expired_models": {
"task": "authentik.core.tasks.clean_expired_models",
"schedule": crontab(minute="*/5"),
"options": {"queue": "authentik_scheduled"},
},
"db_backup": {
"task": "authentik.core.tasks.backup_database",
"schedule": crontab(hour="*/24", minute=0),
"schedule": crontab(minute="2-59/5"),
"options": {"queue": "authentik_scheduled"},
},
"user_cleanup": {
"task": "authentik.core.tasks.clean_temporary_users",
"schedule": crontab(minute="*/5"),
"schedule": crontab(minute="9-59/5"),
"options": {"queue": "authentik_scheduled"},
},
}
@ -361,34 +352,13 @@ CELERY_RESULT_BACKEND = (
)
# Sentry integration
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
env = get_env()
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
if _ERROR_REPORTING:
# pylint: disable=abstract-class-instantiated
sentry_init(
dsn=SENTRY_DSN,
integrations=[
DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(),
RedisIntegration(),
ThreadingIntegration(propagate_hub=True),
],
before_send=before_send,
release=f"authentik@{__version__}",
traces_sample_rate=float(CONFIG.y("error_reporting.sample_rate", 0.5)),
environment=CONFIG.y("error_reporting.environment", "customer"),
send_default_pii=CONFIG.y_bool("error_reporting.send_pii", False),
)
set_tag("authentik.build_hash", get_build_hash("tagged"))
set_tag("authentik.env", env)
set_tag("authentik.component", "backend")
sentry_env = CONFIG.y("error_reporting.environment", "customer")
sentry_init()
set_tag("authentik.uuid", sha512(str(SECRET_KEY).encode("ascii")).hexdigest()[:16])
j_print(
"Error reporting is enabled",
env=CONFIG.y("error_reporting.environment", "customer"),
)
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
@ -465,7 +435,6 @@ _LOGGING_HANDLER_MAP = {
"django": "WARNING",
"celery": "WARNING",
"selenium": "WARNING",
"grpc": LOG_LEVEL,
"docker": "WARNING",
"urllib3": "WARNING",
"websockets": "WARNING",
@ -473,6 +442,7 @@ _LOGGING_HANDLER_MAP = {
"kubernetes": "INFO",
"asyncio": "WARNING",
"aioredis": "WARNING",
"silk": "INFO",
}
for handler_name, level in _LOGGING_HANDLER_MAP.items():
# pyright: reportGeneralTypeIssues=false
@ -509,6 +479,9 @@ for _app in INSTALLED_APPS:
if DEBUG:
CELERY_TASK_ALWAYS_EAGER = True
os.environ[ENV_GIT_HASH_KEY] = "dev"
INSTALLED_APPS.append("silk")
SILKY_PYTHON_PROFILER = True
MIDDLEWARE = ["silk.middleware.SilkyMiddleware"] + MIDDLEWARE
INSTALLED_APPS.append("authentik.core")

View File

@ -4,6 +4,7 @@ from argparse import ArgumentParser
from django.conf import settings
from authentik.lib.config import CONFIG
from authentik.lib.sentry import sentry_init
from tests.e2e.utils import get_docker_tag
@ -15,7 +16,7 @@ class PytestTestRunner: # pragma: no cover
self.failfast = failfast
self.keepdb = keepdb
self.args = ["-vv"]
self.args = ["-vv", "-s"]
if self.failfast:
self.args.append("--exitfirst")
if self.keepdb:
@ -32,6 +33,11 @@ class PytestTestRunner: # pragma: no cover
"outposts.container_image_base",
f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}",
)
sentry_init(
sample_rate=1.0,
environment="testing",
send_default_pii=True,
)
@classmethod
def add_arguments(cls, parser: ArgumentParser):

View File

@ -1,4 +1,5 @@
"""authentik URL Configuration"""
from django.conf import settings
from django.urls import include, path
from structlog.stdlib import get_logger
@ -47,3 +48,8 @@ urlpatterns += [
path("-/health/live/", LiveView.as_view(), name="health-live"),
path("-/health/ready/", ReadyView.as_view(), name="health-ready"),
]
if settings.DEBUG:
urlpatterns += [
path("debug/silk/", include("silk.urls", namespace="silk")),
]

View File

@ -1,10 +1,12 @@
"""LDAP Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"sources_ldap_sync": {
"task": "authentik.sources.ldap.tasks.ldap_sync_all",
"schedule": crontab(minute="*/120"), # Run every other hour
"schedule": crontab(minute=fqdn_rand("sources_ldap_sync"), hour="*/2"),
"options": {"queue": "authentik_scheduled"},
}
}

View File

@ -4,12 +4,12 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.sources import UserSourceConnectionSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.oauth.models import UserOAuthSourceConnection
class UserOAuthSourceConnectionSerializer(SourceSerializer):
class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
"""OAuth Source Serializer"""
class Meta:

View File

@ -11,6 +11,7 @@ from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.base import BaseOAuthClient
LOGGER = get_logger()
SESSION_OAUTH_PKCE = "oauth_pkce"
class OAuth2Client(BaseOAuthClient):
@ -69,6 +70,8 @@ class OAuth2Client(BaseOAuthClient):
"code": code,
"grant_type": "authorization_code",
}
if SESSION_OAUTH_PKCE in self.request.session:
args["code_verifier"] = self.request.session[SESSION_OAUTH_PKCE]
try:
access_token_url = self.source.type.access_token_url or ""
if self.source.type.urls_customizable and self.source.access_token_url:

View File

@ -152,7 +152,7 @@ class DiscordOAuthSource(OAuthSource):
class GoogleOAuthSource(OAuthSource):
"""Social Login using Google or Gsuite."""
"""Social Login using Google or Google Workspace (GSuite)."""
class Meta:
@ -182,7 +182,7 @@ class OpenIDConnectOAuthSource(OAuthSource):
class AppleOAuthSource(OAuthSource):
"""Login using a apple.com."""
"""Social Login using Apple."""
class Meta:
@ -192,7 +192,7 @@ class AppleOAuthSource(OAuthSource):
class OktaOAuthSource(OAuthSource):
"""Login using a okta.com."""
"""Social Login using Okta."""
class Meta:

View File

@ -4,88 +4,8 @@ from django.test import TestCase
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
# https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/ \
# api-reference/get-account-verify_credentials
TWITTER_USER = {
"contributors_enabled": True,
"created_at": "Sat May 09 17:58:22 +0000 2009",
"default_profile": False,
"default_profile_image": False,
"description": "I taught your phone that thing you like.",
"favourites_count": 588,
"follow_request_sent": None,
"followers_count": 10625,
"following": None,
"friends_count": 1181,
"geo_enabled": True,
"id": 38895958,
"id_str": "38895958",
"is_translator": False,
"lang": "en",
"listed_count": 190,
"location": "San Francisco",
"name": "Sean Cook",
"notifications": None,
"profile_background_color": "1A1B1F",
"profile_background_image_url": "",
"profile_background_image_url_https": "",
"profile_background_tile": True,
"profile_image_url": "",
"profile_image_url_https": "",
"profile_link_color": "2FC2EF",
"profile_sidebar_border_color": "181A1E",
"profile_sidebar_fill_color": "252429",
"profile_text_color": "666666",
"profile_use_background_image": True,
"protected": False,
"screen_name": "theSeanCook",
"show_all_inline_media": True,
"status": {
"contributors": None,
"coordinates": {"coordinates": [-122.45037293, 37.76484123], "type": "Point"},
"created_at": "Tue Aug 28 05:44:24 +0000 2012",
"favorited": False,
"geo": {"coordinates": [37.76484123, -122.45037293], "type": "Point"},
"id": 240323931419062272,
"id_str": "240323931419062272",
"in_reply_to_screen_name": "messl",
"in_reply_to_status_id": 240316959173009410,
"in_reply_to_status_id_str": "240316959173009410",
"in_reply_to_user_id": 18707866,
"in_reply_to_user_id_str": "18707866",
"place": {
"attributes": {},
"bounding_box": {
"coordinates": [
[
[-122.45778216, 37.75932999],
[-122.44248216, 37.75932999],
[-122.44248216, 37.76752899],
[-122.45778216, 37.76752899],
]
],
"type": "Polygon",
},
"country": "United States",
"country_code": "US",
"full_name": "Ashbury Heights, San Francisco",
"id": "866269c983527d5a",
"name": "Ashbury Heights",
"place_type": "neighborhood",
"url": "http://api.twitter.com/1/geo/id/866269c983527d5a.json",
},
"retweet_count": 0,
"retweeted": False,
"source": "Twitter for iPhone",
"text": "@messl congrats! So happy for all 3 of you.",
"truncated": False,
},
"statuses_count": 2609,
"time_zone": "Pacific Time (US & Canada)",
"url": None,
"utc_offset": -28800,
"verified": False,
}
# https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
TWITTER_USER = {"data": {"id": "2244994945", "name": "TwitterDev", "username": "Twitter Dev"}}
class TestTypeGitHub(TestCase):
@ -104,6 +24,6 @@ class TestTypeGitHub(TestCase):
def test_enroll_context(self):
"""Test Twitter Enrollment context"""
ak_context = TwitterOAuthCallback().get_user_enroll_context(TWITTER_USER)
self.assertEqual(ak_context["username"], TWITTER_USER["screen_name"])
self.assertEqual(ak_context["email"], TWITTER_USER.get("email", None))
self.assertEqual(ak_context["name"], TWITTER_USER["name"])
self.assertEqual(ak_context["username"], TWITTER_USER["data"]["username"])
self.assertEqual(ak_context["email"], None)
self.assertEqual(ak_context["name"], TWITTER_USER["data"]["name"])

View File

@ -1,21 +1,46 @@
"""Twitter OAuth Views"""
from typing import Any
from authentik.lib.generators import generate_id
from authentik.sources.oauth.clients.oauth2 import SESSION_OAUTH_PKCE
from authentik.sources.oauth.types.azure_ad import AzureADClient
from authentik.sources.oauth.types.manager import MANAGER, SourceType
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
class TwitterOAuthRedirect(OAuthRedirect):
"""Twitter OAuth2 Redirect"""
def get_additional_parameters(self, source): # pragma: no cover
self.request.session[SESSION_OAUTH_PKCE] = generate_id()
return {
"scope": ["users.read", "tweet.read"],
"code_challenge": self.request.session[SESSION_OAUTH_PKCE],
"code_challenge_method": "plain",
}
class TwitterOAuthCallback(OAuthCallback):
"""Twitter OAuth2 Callback"""
# Twitter has the same quirk as azure and throws an error if the access token
# is set via query parameter, so we re-use the azure client
# see https://github.com/goauthentik/authentik/issues/1910
client_class = AzureADClient
def get_user_id(self, info: dict[str, str]) -> str:
return info.get("data", {}).get("id", "")
def get_user_enroll_context(
self,
info: dict[str, Any],
) -> dict[str, Any]:
data = info.get("data", {})
return {
"username": info.get("screen_name"),
"email": info.get("email", None),
"name": info.get("name"),
"username": data.get("username"),
"email": None,
"name": data.get("name"),
}
@ -24,10 +49,10 @@ class TwitterType(SourceType):
"""Twitter Type definition"""
callback_view = TwitterOAuthCallback
redirect_view = TwitterOAuthRedirect
name = "Twitter"
slug = "twitter"
request_token_url = "https://api.twitter.com/oauth/request_token" # nosec
authorization_url = "https://api.twitter.com/oauth/authenticate"
access_token_url = "https://api.twitter.com/oauth/access_token" # nosec
profile_url = "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true"
authorization_url = "https://twitter.com/i/oauth2/authorize"
access_token_url = "https://api.twitter.com/2/oauth2/token" # nosec
profile_url = "https://api.twitter.com/2/users/me"

View File

@ -4,12 +4,12 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.sources import UserSourceConnectionSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.sources.plex.models import PlexSourceConnection
class PlexSourceConnectionSerializer(SourceSerializer):
class PlexSourceConnectionSerializer(UserSourceConnectionSerializer):
"""Plex Source connection Serializer"""
class Meta:

View File

@ -1,10 +1,12 @@
"""Plex source settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"check_plex_token": {
"task": "authentik.sources.plex.tasks.check_plex_token_all",
"schedule": crontab(minute="31", hour="*/3"),
"schedule": crontab(minute=fqdn_rand("check_plex_token"), hour="*/3"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -29,6 +29,19 @@ class TestMetadataProcessor(TestCase):
schema = etree.XMLSchema(etree.parse("xml/saml-schema-metadata-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata))
def test_metadata_consistent(self):
"""Test Metadata generation being consistent (xml stays the same)"""
source = SAMLSource.objects.create(
slug="provider",
issuer="authentik",
signing_kp=create_test_cert(),
pre_authentication_flow=create_test_flow(),
)
request = self.factory.get("/")
xml_a = MetadataProcessor(source, request).build_entity_descriptor()
xml_b = MetadataProcessor(source, request).build_entity_descriptor()
self.assertEqual(xml_a, xml_b)
def test_metadata(self):
"""Test Metadata generation being valid"""
source = SAMLSource.objects.create(

View File

@ -13,7 +13,15 @@ from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from xmlsec import InternalError, VerificationError
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.challenge import (
PLAN_CONTEXT_ATTRS,
PLAN_CONTEXT_TITLE,
PLAN_CONTEXT_URL,
AutosubmitChallenge,
Challenge,
ChallengeResponse,
ChallengeTypes,
)
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import (
PLAN_CONTEXT_REDIRECT,
@ -26,7 +34,6 @@ from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSI
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64
from authentik.providers.saml.views.flows import AutosubmitChallenge
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
from authentik.sources.saml.processors.metadata import MetadataProcessor
@ -38,9 +45,6 @@ from authentik.stages.consent.stage import (
ConsentStageView,
)
PLAN_CONTEXT_TITLE = "title"
PLAN_CONTEXT_URL = "url"
PLAN_CONTEXT_ATTRS = "attrs"
LOGGER = get_logger()

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-10 17:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_duo", "0002_default_setup_flow"),
]
operations = [
migrations.AddField(
model_name="duodevice",
name="last_t",
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -74,6 +74,8 @@ class DuoDevice(Device):
stage = models.ForeignKey(AuthenticatorDuoStage, on_delete=models.CASCADE)
duo_user_id = models.TextField()
last_t = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name or str(self.user)

View File

@ -1,5 +1,6 @@
"""Duo stage"""
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import now
from rest_framework.fields import CharField
from structlog.stdlib import get_logger
@ -85,7 +86,11 @@ class AuthenticatorDuoStageView(ChallengeStageView):
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE)
if not existing_device:
DuoDevice.objects.create(
name="Duo Device", user=self.get_pending_user(), duo_user_id=user_id, stage=stage
name="Duo Device",
user=self.get_pending_user(),
duo_user_id=user_id,
stage=stage,
last_t=now(),
)
else:
return self.executor.stage_invalid("Device with Credential ID already exists.")

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-04-14 20:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_sms", "0002_alter_authenticatorsmsstage_from_number"),
]
operations = [
migrations.AddField(
model_name="smsdevice",
name="last_t",
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -168,6 +168,14 @@ class SMSDevice(SideChannelDevice):
phone_number = models.TextField()
last_t = models.DateTimeField(auto_now=True)
def verify_token(self, token):
valid = super().verify_token(token)
if valid:
self.save()
return valid
def __str__(self):
return self.name or str(self.user)

View File

@ -46,6 +46,7 @@ class AuthenticatorSMSStageTests(APITestCase):
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
"layout": "stacked",
},
"pending_user": "foo",
"pending_user_avatar": "/static/dist/assets/images/user_default.png",

View File

@ -5,13 +5,10 @@ from rest_framework.fields import CharField, ListField
from structlog.stdlib import get_logger
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
LOGGER = get_logger()
SESSION_STATIC_DEVICE = "static_device"
SESSION_STATIC_TOKENS = "static_device_tokens"
class AuthenticatorStaticChallenge(WithUserInfoChallenge):
@ -33,7 +30,8 @@ class AuthenticatorStaticStageView(ChallengeStageView):
response_class = AuthenticatorStaticChallengeResponse
def get_challenge(self, *args, **kwargs) -> AuthenticatorStaticChallenge:
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
user = self.get_pending_user()
tokens: list[StaticToken] = StaticToken.objects.filter(device__user=user)
return AuthenticatorStaticChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
@ -42,34 +40,32 @@ class AuthenticatorStaticStageView(ChallengeStageView):
)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
user = self.get_pending_user()
if not user.is_authenticated:
LOGGER.debug("No pending user, continuing")
return self.executor.stage_ok()
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if StaticDevice.objects.filter(user=user).exists():
return self.executor.stage_ok()
stage: AuthenticatorStaticStage = self.executor.current_stage
if SESSION_STATIC_DEVICE not in self.request.session:
device = StaticDevice(user=user, confirmed=False, name="Static Token")
tokens = []
for _ in range(0, stage.token_count):
tokens.append(StaticToken(device=device, token=StaticToken.random_token()))
self.request.session[SESSION_STATIC_DEVICE] = device
self.request.session[SESSION_STATIC_TOKENS] = tokens
devices = StaticDevice.objects.filter(user=user)
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if devices.exists():
if not any(x.confirmed for x in devices):
return super().get(request, *args, **kwargs)
return self.executor.stage_ok()
device = StaticDevice.objects.create(user=user, confirmed=False, name="Static Token")
for _ in range(0, stage.token_count):
StaticToken.objects.create(device=device, token=StaticToken.random_token())
return super().get(request, *args, **kwargs)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
"""Verify OTP Token"""
device: StaticDevice = self.request.session[SESSION_STATIC_DEVICE]
user = self.get_pending_user()
device: StaticDevice = StaticDevice.objects.filter(user=user).first()
if not device:
return self.executor.stage_invalid()
device.confirmed = True
device.save()
for token in self.request.session[SESSION_STATIC_TOKENS]:
token.save()
del self.request.session[SESSION_STATIC_DEVICE]
del self.request.session[SESSION_STATIC_TOKENS]
return self.executor.stage_ok()

View File

@ -14,13 +14,11 @@ from authentik.flows.challenge import (
ChallengeTypes,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER
LOGGER = get_logger()
SESSION_TOTP_DEVICE = "totp_device"
class AuthenticatorTOTPChallenge(WithUserInfoChallenge):
@ -40,10 +38,11 @@ class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
def validate_code(self, code: int) -> int:
"""Validate totp code"""
if self.device is not None:
if not self.device.verify_token(code):
self.device.confirmed = False
raise ValidationError(_("Code does not match"))
if not self.device:
raise ValidationError(_("Code does not match"))
if not self.device.verify_token(code):
self.device.confirmed = False
raise ValidationError(_("Code does not match"))
return code
@ -53,7 +52,8 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
response_class = AuthenticatorTOTPChallengeResponse
def get_challenge(self, *args, **kwargs) -> Challenge:
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
user = self.get_pending_user()
device: TOTPDevice = TOTPDevice.objects.filter(user=user).first()
return AuthenticatorTOTPChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
@ -65,34 +65,37 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
def get_response_instance(self, data: QueryDict) -> ChallengeResponse:
response = super().get_response_instance(data)
response.device = self.request.session[SESSION_TOTP_DEVICE]
user = self.get_pending_user()
response.device = TOTPDevice.objects.filter(user=user).first()
return response
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
user = self.get_pending_user()
if not user.is_authenticated:
LOGGER.debug("No pending user, continuing")
return self.executor.stage_ok()
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if TOTPDevice.objects.filter(user=user).exists():
return self.executor.stage_ok()
stage: AuthenticatorTOTPStage = self.executor.current_stage
if SESSION_TOTP_DEVICE not in self.request.session:
device = TOTPDevice(
user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator"
)
devices = TOTPDevice.objects.filter(user=user)
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if devices.exists():
if not any(x.confirmed for x in devices):
return super().get(request, *args, **kwargs)
return self.executor.stage_ok()
self.request.session[SESSION_TOTP_DEVICE] = device
TOTPDevice.objects.create(
user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator"
)
return super().get(request, *args, **kwargs)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
"""TOTP Token is validated by challenge"""
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
user = self.get_pending_user()
device: TOTPDevice = TOTPDevice.objects.filter(user=user).first()
if not device:
return self.executor.stage_invalid()
device.confirmed = True
device.save()
del self.request.session[SESSION_TOTP_DEVICE]
return self.executor.stage_ok()

View File

@ -30,6 +30,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
"not_configured_action",
"device_classes",
"configuration_stages",
"last_auth_threshold",
]

View File

@ -147,4 +147,5 @@ def validate_challenge_duo(device_pk: int, request: HttpRequest, user: User) ->
# {'result': 'allow', 'status': 'allow', 'status_msg': 'Success. Logging you in...'}
if response["result"] == "deny":
raise ValidationError("Duo denied access")
device.save()
return device_pk

View File

@ -0,0 +1,27 @@
# Generated by Django 4.0.4 on 2022-04-14 20:54
from django.db import migrations, models
import authentik.lib.utils.time
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_validate",
"0010_remove_authenticatorvalidatestage_configuration_stage_and_more",
),
]
operations = [
migrations.AddField(
model_name="authenticatorvalidatestage",
name="last_auth_threshold",
field=models.TextField(
default="seconds=0",
help_text="If any of the user's device has been used within this threshold, this stage will be skipped",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
]

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