Compare commits

...

270 Commits

Author SHA1 Message Date
2e659c1ab0 release: 2021.3.1-rc1 2021-03-02 09:41:09 +01:00
ad0cc5f0be Merge branch 'master' into version-2021.3 2021-03-02 09:41:00 +01:00
7ae9482e7b web: fix mismatched version 2021-03-02 09:40:43 +01:00
7fb95dfabf stages/password: improve logging 2021-03-02 09:40:32 +01:00
83cc5d24f2 stages/password: improve logging 2021-03-02 09:30:29 +01:00
38b3096c9a build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#603)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-02 09:20:53 +01:00
df8f21e559 build(deps): bump @sentry/tracing from 6.2.0 to 6.2.1 in /web (#604)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.2.0...6.2.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-02 09:20:45 +01:00
f4979fcf19 build(deps-dev): bump @typescript-eslint/parser in /web (#605)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-02 09:13:01 +01:00
431b7375c1 build(deps): bump @sentry/browser from 6.2.0 to 6.2.1 in /web (#606)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.2.0...6.2.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-02 09:12:45 +01:00
a6627145c8 build(deps): bump boto3 from 1.17.17 to 1.17.18 (#607)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.17 to 1.17.18.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.17...1.17.18)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-02 09:12:28 +01:00
3045cf1aef web: make user password-reset button use action button 2021-03-01 20:41:54 +01:00
c65b2944b3 stages/reputation: add API for user and IP Score 2021-03-01 20:22:37 +01:00
2ae5a81c15 stages/deny: add deny stage 2021-03-01 20:16:54 +01:00
ed8b78600e stages/authenticator_validate: add configuration stage to configure Authenticator 2021-03-01 19:23:59 +01:00
644a03e40e lib: don't order_by on widget because PolicyBindingModel, order in form 2021-03-01 19:23:09 +01:00
88ce93ab04 policies: fix tests creating policies with empty names 2021-03-01 19:22:35 +01:00
8878dc61d3 web: Add Policy create button to BoundPoliciesList 2021-03-01 18:34:36 +01:00
03d38557e5 stages/*: simplify __str__ of classes 2021-03-01 18:30:47 +01:00
37b59bb5b9 lib: sort GroupedModelChoiceField by name
closes #602
2021-03-01 18:30:27 +01:00
19eea68e0f web: use vertical list for related application links 2021-03-01 17:23:00 +01:00
ce7aae16c9 stages/password: fix ?next param for password change 2021-03-01 17:17:44 +01:00
fd9ba97479 core: remove source's ui_additional_info 2021-03-01 16:57:51 +01:00
919debdd13 root: lock twisted to 20.3.0 whilst its causing issues with uvloop 2021-03-01 16:26:05 +01:00
36690de285 root: push PR and branch images to GHCR (#601) 2021-03-01 12:04:56 +01:00
ca4ead8fd8 events: fix event creation with anonymous user 2021-03-01 12:04:27 +01:00
a81f981471 lib: fix being unable to set authentik. options 2021-03-01 11:11:00 +01:00
d6fd2b0afa sources/saml: add Metadata API 2021-03-01 10:50:45 +01:00
0478ae3da8 web: add missing SAML Source display page 2021-03-01 10:49:00 +01:00
9c33f4858f web: fix more height issues and sidebar not correctly activating 2021-03-01 10:32:25 +01:00
f2eaa9052e build(deps-dev): bump coverage from 5.4 to 5.5 (#598)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.4 to 5.5.
- [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/coverage-5.4...coverage-5.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 09:01:08 +01:00
21d0641110 build(deps): bump rollup from 2.39.1 to 2.40.0 in /web (#599)
Bumps [rollup](https://github.com/rollup/rollup) from 2.39.1 to 2.40.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.39.1...v2.40.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 09:00:39 +01:00
67d05f99e9 build(deps-dev): bump eslint from 7.20.0 to 7.21.0 in /web (#600)
Bumps [eslint](https://github.com/eslint/eslint) from 7.20.0 to 7.21.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.20.0...v7.21.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 09:00:31 +01:00
21d6a28715 root: fix branch Name extraction to work on non PRs 2021-02-28 14:24:16 +01:00
1149a8d9a4 flows: fix tests for diagram 2021-02-28 11:01:34 +01:00
5e98172afb website/docs: update next docs 2021-02-28 10:44:00 +01:00
9b3e94c7c8 flows: fix flow diagram showing policy after stage and not before 2021-02-28 10:28:37 +01:00
30a1b65e94 web: fix BoundPoliciesList not showing policy correctly 2021-02-28 10:19:19 +01:00
9bb46ecb88 build(deps): bump boto3 from 1.17.16 to 1.17.17 (#597) 2021-02-28 09:47:31 +01:00
269e6c4f38 Merge pull request #564 from BeryJu/stage-challenge 2021-02-28 01:30:52 +01:00
7f65ae3f92 Merge branch 'master' into stage-challenge
# Conflicts:
#	web/package-lock.json
2021-02-28 00:47:18 +01:00
ee6b365003 tests/e2e: fix SAML source tests 2021-02-28 00:30:33 +01:00
2ad4bd5c0a tests/e2e: fix oauth source tests 2021-02-28 00:27:18 +01:00
0958740b51 providers/saml: fix Autosubmit Challenge 2021-02-28 00:09:08 +01:00
9cdfd8b75a tests/e2e: fix other provider tests 2021-02-28 00:08:57 +01:00
3c8a0081bc tests/e2e: fix more test, add requirements for crypto 2021-02-27 23:33:15 +01:00
088e0e736a tests/e2e: fix more tests, cleanup 2021-02-27 22:57:15 +01:00
cbb0681f95 web/stages/consent: use data- attribute for permission id 2021-02-27 22:54:20 +01:00
55c408a8bf tests/e2e: replace apply_default_data with @apply_migration decorator 2021-02-27 22:32:48 +01:00
07379acf7f tests/e2e: fix enroll tests 2021-02-27 22:09:40 +01:00
a1af93f8be web: disable CaptureConsoleIntegration 2021-02-27 22:09:10 +01:00
b9a9da4ec7 web/stages/prompt: render later so fields aren't cached 2021-02-27 21:05:20 +01:00
05a5b5b675 stages/prompt: fix fields not being sorted correctly 2021-02-27 21:03:57 +01:00
0fb17eee43 web: fix URLs for clear buttons 2021-02-27 21:00:55 +01:00
a1474e09e5 website/docs: fix type for username input 2021-02-27 21:00:42 +01:00
a33c7d7786 web: fix admin url for Prompt 2021-02-27 21:00:33 +01:00
c08d9762d9 web: add button to edit stage on flow BoundStageList 2021-02-27 20:45:59 +01:00
d43e6e5736 web: fix styling on dark mode for band on flow 2021-02-27 20:45:46 +01:00
380786bfde tests/e2e: fix URLs, use self.login() 2021-02-27 19:02:29 +01:00
ffcf064f83 Merge branch 'version-2021.2'
# Conflicts:
#	authentik/policies/group_membership/forms.py
#	web/package.json
#	web/src/constants.ts
#	web/src/elements/buttons/TokenCopyButton.ts
2021-02-27 18:29:30 +01:00
252718bbaf docs: add changelog for 2021.2.6 2021-02-27 18:18:36 +01:00
402afa1e85 Merge branch 'master' into stage-challenge
# Conflicts:
#	web/src/elements/buttons/SpinnerButton.ts
2021-02-27 18:10:08 +01:00
5b4e75000b web: fix colourstyles not being included in common_styles 2021-02-27 17:38:21 +01:00
1a052913e9 web: add sentry CaptureConsole 2021-02-27 17:18:42 +01:00
e930a1d0dc root: run ci on version-* branches 2021-02-27 17:07:11 +01:00
4cbfaaa72b sources/ldap: fix password setter on users which are not LDAP 2021-02-27 16:49:59 +01:00
92943f08d9 sources/ldap: fix API error when source has not synced yet 2021-02-27 16:29:36 +01:00
10ef1c7e93 admin: fix missing success_url for clean views 2021-02-27 16:27:42 +01:00
02c762c268 flows: handle error when app cannot be found during import 2021-02-27 16:26:06 +01:00
bbf0ca92af website/flows: fix default value for session_duration 2021-02-27 16:09:58 +01:00
d2dfc6d63b Merge branch 'master' into stage-challenge 2021-02-27 16:04:57 +01:00
a18240fcd7 providers/oauth2: fix error when no login event could be found 2021-02-27 16:02:07 +01:00
d36e5dccf9 docs: add docs for recovery keys 2021-02-27 16:00:05 +01:00
9af1d6f63b events: fix error when event can't be loaded into rule task 2021-02-27 15:22:43 +01:00
ab6d46558b docs: remove dummy stage 2021-02-27 15:20:33 +01:00
e94abfc986 flows: fix glob pattern for doc flows 2021-02-27 13:23:16 +01:00
5c652c1f79 policies: sort groups in groupmembership policy and binding
closes #595
2021-02-27 13:19:38 +01:00
89aa0f0cc8 Merge branch 'master' into stage-challenge 2021-02-27 13:14:38 +01:00
085589bcec docs(exampleFlows): Rename pbflow to akflow (#596)
* docs(exampleFlows): Rename enrollment-2-stage.pbflow

* docs(exampleFlows): Rename enrollment-email-verification.pbflow

* docs(exampleFlows): Rename login-2fa.pbflow

* docs(exampleFlows): Rename login-conditional-captcha.pbflow

* docs(exampleFlows): Rename recovery-email-verification.pbflow

* docs(exampleFlows): Rename unenrollment.pbflow

* docs(exampleFlows): Fix links.
2021-02-27 13:11:41 +01:00
95d0d6f3e8 tests/e2e: add login method 2021-02-26 16:47:01 +01:00
c62ef4ae81 stages/identification: don't pass entire application 2021-02-26 16:44:45 +01:00
3df81ca6f0 root: don't use failfast 2021-02-26 15:39:50 +01:00
578326eccd tests/e2e: don't use reverse for self.shell_url 2021-02-26 15:13:31 +01:00
2335ccddaa root: add fallback URL for websocket 2021-02-26 15:12:58 +01:00
477e30f542 core: fix path for blank avatar when avatars are disabled 2021-02-26 15:07:30 +01:00
7bf3d7e10a Merge branch 'master' into stage-challenge
# Conflicts:
#	Pipfile.lock
2021-02-26 12:02:49 +01:00
1bef659b10 stages/captcha: remove dependency on captcha app 2021-02-26 10:13:58 +01:00
e3f7bd8ab8 build(deps): bump @types/chart.js from 2.9.30 to 2.9.31 in /web (#593)
Bumps [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js) from 2.9.30 to 2.9.31.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-26 09:25:55 +01:00
45c731de3c build(deps): bump boto3 from 1.17.15 to 1.17.16 (#594)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.15 to 1.17.16.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.15...1.17.16)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-26 09:25:44 +01:00
535770abbd tests/e2e: fix authenticator tests 2021-02-25 22:54:39 +01:00
eccea8eba0 web/stages/authenticator_totp: fix enter button triggering copy button 2021-02-25 22:43:42 +01:00
ab200a1dfb web: add hidden input with totp config url 2021-02-25 21:43:00 +01:00
ca122b20c9 web: fix spacing for messages 2021-02-25 21:23:31 +01:00
74b407ebc7 web: add button to copy TOTP URL
closes #591
2021-02-25 21:22:57 +01:00
fbf2fe2404 stages/authenticator_validate: fix webauthn validation 2021-02-25 21:00:32 +01:00
b968adffc1 stages/authenticator_totp: fix error when disabling device 2021-02-25 21:00:18 +01:00
c275992f7b web: re-enable fallback to siteshell for now 2021-02-25 20:55:22 +01:00
4e2c686db1 Merge branch 'master' into stage-challenge
# Conflicts:
#	Pipfile.lock
2021-02-25 20:13:17 +01:00
bfc69562d8 root: remove qr and captcha from backend dependencies 2021-02-25 20:06:20 +01:00
9e6a7bf16b stages/captcha: migrated to SPA 2021-02-25 19:58:38 +01:00
890e0e9054 *: remove unused templates and code, move avatar to User model 2021-02-25 19:58:23 +01:00
cf7e7c44ff stages/password: fix recovery link not being shown in SPA 2021-02-25 15:09:48 +01:00
0f169f176d stages/authenticator_validate: implement validation, add button to go back to device picker 2021-02-25 12:07:32 +01:00
429fc921b1 build(deps): bump codemirror from 5.59.3 to 5.59.4 in /web (#590)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.59.3 to 5.59.4.
- [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.59.3...5.59.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-25 09:18:15 +01:00
e7a9a41a2f build(deps): bump boto3 from 1.17.14 to 1.17.15 (#589)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.14 to 1.17.15.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.14...1.17.15)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-25 09:18:02 +01:00
d1c24f47b2 build(deps): bump docker from 4.4.3 to 4.4.4 (#588)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.3 to 4.4.4.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.3...4.4.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-25 09:17:45 +01:00
007676b400 stages/authenticator_validate: fix post from code form 2021-02-24 20:39:39 +01:00
c0c235bead helm: fix initialDelaySeconds 2021-02-24 19:39:35 +01:00
a3aacb5285 docs: add 2021.2.5 2021-02-24 10:22:55 +01:00
5977c09b05 Merge branch 'version-2021.2'
# Conflicts:
#	authentik/admin/templates/administration/policy/list.html
#	web/src/pages/outposts/OutpostListPage.ts
2021-02-24 10:20:48 +01:00
9049593ff5 sources/oauth: fix buttons not being ak-root-link 2021-02-24 09:23:19 +01:00
e74c098b7a web: fix library not being full height, again 2021-02-24 09:21:16 +01:00
d06a44378d build(deps): bump boto3 from 1.17.13 to 1.17.14 (#586)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.13 to 1.17.14.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.13...1.17.14)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-24 09:13:20 +01:00
0a8da376fc build(deps-dev): bump typescript from 4.1.5 to 4.2.2 in /web (#587)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.1.5 to 4.2.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/commits)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-24 09:13:07 +01:00
2a0f940a42 Merge branch 'master' into stage-challenge 2021-02-23 23:43:51 +01:00
8aa067795a web: replace default flow background for 2021.3 2021-02-23 23:43:37 +01:00
3cdb81c5ba stages/authenticator_validate: create challenge per device, implement class switcher 2021-02-23 23:43:13 +01:00
e8259791f0 stages/authenticator_webauthn: fix rp_id and origin generation 2021-02-23 23:39:00 +01:00
55af786852 Merge branch 'master' into stage-challenge 2021-02-23 23:17:03 +01:00
8a916602c4 docs: update for 2021.2.4-stable 2021-02-23 23:16:32 +01:00
7101c7987c Merge branch 'version-2021.2' 2021-02-23 23:02:05 +01:00
66e5958283 core: fix user-settings not loading sources 2021-02-23 22:53:17 +01:00
9db445c3ee admin: fix success_urls 2021-02-23 22:53:05 +01:00
574438b51e web: fix /-/user/ not loading 2021-02-23 22:46:11 +01:00
a05885140d Merge branch 'master' into stage-challenge 2021-02-23 18:27:04 +01:00
8878fac4e7 stages/authenticator_validate: send challenge for each device 2021-02-23 18:25:58 +01:00
7ee97a961c build(deps): bump github.com/getsentry/sentry-go in /outpost (#584)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-go/compare/v0.9.0...v0.10.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 16:25:52 +01:00
737ff62e92 build(deps): bump golang from 1.15 to 1.16.0 in /outpost (#582)
Bumps golang from 1.15 to 1.16.0.

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 16:19:31 +01:00
07ada5a1b7 build(deps): bump rollup from 2.39.0 to 2.39.1 in /web (#583)
Bumps [rollup](https://github.com/rollup/rollup) from 2.39.0 to 2.39.1.
- [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.39.0...v2.39.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 16:19:24 +01:00
8caeed6b18 build(deps): bump github.com/sirupsen/logrus in /outpost (#585)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.7.0...v1.8.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 16:19:10 +01:00
b5adff5327 root: fix paths for dependabot 2021-02-23 15:20:10 +01:00
3894895d32 stages/authenticator_validate: start rewrite to SPA 2021-02-23 13:50:47 +01:00
7f53c97fb2 Merge branch 'master' into stage-challenge 2021-02-23 13:18:35 +01:00
44bd4b9511 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#580)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.1 to 4.15.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.15.2/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 10:17:21 +01:00
2a1b5e0154 docs: add hardware requirements to docker-compose 2021-02-23 09:46:07 +01:00
8c0d48fe0a build(deps): bump boto3 from 1.17.12 to 1.17.13 (#579)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.12 to 1.17.13.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.12...1.17.13)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 09:25:56 +01:00
0863e60d29 build(deps-dev): bump @typescript-eslint/parser in /web (#581)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.15.1 to 4.15.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.15.2/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-23 09:25:36 +01:00
451c117ea4 stages/authenticator_webauthn: fix incorrect response being sent 2021-02-22 19:54:05 +01:00
388c8c8bec web: fix autofocus in ak-form-element 2021-02-22 19:26:50 +01:00
5904070bb2 flows: add ensure_csrf_cookie() to flow shell view 2021-02-22 18:42:15 +01:00
35ac87ec10 Merge branch 'master' into stage-challenge 2021-02-22 17:42:12 +01:00
8f8c2a291b root: add discord badge to readme 2021-02-22 17:27:16 +01:00
592a2dcede build(deps): bump codemirror from 5.59.2 to 5.59.3 in /web (#577)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.59.2 to 5.59.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.59.2...5.59.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-22 08:44:18 +01:00
a3221475e5 build(deps): bump boto3 from 1.17.11 to 1.17.12 (#575)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.11 to 1.17.12.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.11...1.17.12)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-22 08:44:10 +01:00
25f5031422 build(deps): bump django from 3.1.6 to 3.1.7 (#574)
Bumps [django](https://github.com/django/django) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.6...3.1.7)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-22 08:44:02 +01:00
63b94263af build(deps): bump uvicorn from 0.13.3 to 0.13.4 (#576)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.13.3 to 0.13.4.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.13.3...0.13.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-22 08:43:52 +01:00
217595bb01 tests: start fixing selenium tests 2021-02-21 23:39:02 +01:00
2dd8119abe web: fix linting 2021-02-21 23:26:16 +01:00
20e0fe3941 web: move ak-form-element to separate file 2021-02-21 23:09:08 +01:00
0fa97de06e web/stages: fix cancel URL 2021-02-21 22:10:32 +01:00
38da13fea3 web: use ak-spinner everywhere 2021-02-21 22:01:48 +01:00
fb9880bff4 web: add loading animation to flowexecutor 2021-02-21 22:01:35 +01:00
acc790f590 stages/authenticator_webauthn: fix formatting of card 2021-02-21 21:42:40 +01:00
76c572cf7c stages/authenticator_webauthn: migrate to SPA 2021-02-21 20:53:23 +01:00
0904fea109 flows: fix migration for e2e tests 2021-02-21 20:47:23 +01:00
6df89e7abf stages/authenticator_static: migrate to SPA 2021-02-21 19:34:49 +01:00
21afda6dc2 stages/authenticator_totp: migrate to SPA 2021-02-21 19:10:50 +01:00
74c0ed27ba web: fix /-/user/ not loading 2021-02-21 19:03:43 +01:00
dc680a3385 flows: set pending_user in ChallengeStageView 2021-02-21 18:58:08 +01:00
88e5b22d16 flows: add get_pending_user() for WithUserInfoChallenge 2021-02-21 18:35:21 +01:00
27cd10e072 stages/prompt: migrate to SPA 2021-02-21 18:27:34 +01:00
d35f524865 core: remove old templates 2021-02-21 14:59:37 +01:00
ca223fa4df providers/saml: migrate to challenge for submit 2021-02-21 14:36:22 +01:00
14962eb6cc stages/email: migrate to SPA 2021-02-21 13:42:55 +01:00
b9f409d6d9 stages/consent: migrate to SPA 2021-02-21 13:15:45 +01:00
a8681ac88f root: fix typo 2021-02-21 00:22:14 +01:00
c1e6786ea1 stages/password: Migrate to SPA 2021-02-21 00:14:42 +01:00
1c8d101fc3 stages/identification: remove templates 2021-02-20 23:47:18 +01:00
7a9140bdcd core: move UILoginButtonSerializer into core 2021-02-20 23:42:35 +01:00
511f94fc7f *: cleanup code, return errors in challenge_invalid, fixup rendering 2021-02-20 23:34:10 +01:00
548b1ead2f flows: challenge error response in challenge format 2021-02-20 20:28:57 +01:00
33f67140f2 stages/identification: move user validation to serializer 2021-02-20 20:16:20 +01:00
8787dc23d0 web: fix linting errors 2021-02-20 19:59:24 +01:00
e0ae92ccc7 stages/*: update tests for new response 2021-02-20 19:41:48 +01:00
bdb86d7119 *: replace shortcuts.reverse with urls.reverse 2021-02-20 19:13:50 +01:00
a1a3d316e3 stages/identification: implement challenge 2021-02-20 18:28:11 +01:00
672b86ef88 web: fix old Links 2021-02-20 18:27:19 +01:00
a3c9d5873c web: fix URL for flows cached count 2021-02-20 18:27:03 +01:00
0e975757b8 core: remove UILoginButton's icon_path 2021-02-20 18:26:43 +01:00
391ee10cb8 Merge branch 'master' into stage-challenge
# Conflicts:
#	authentik/api/v2/urls.py
2021-02-20 12:51:10 +01:00
4f374c0c01 web: add 404 page, don't auto-redirect to home 2021-02-20 00:28:00 +01:00
dde303f13a admin: remove dead code 2021-02-20 00:27:22 +01:00
264c678eaa web: migrate user token list to web 2021-02-20 00:20:01 +01:00
854d94056e web: migrate remaining list views to web 2021-02-20 00:19:53 +01:00
9d4c22c706 web: show header while loading application info 2021-02-19 23:34:06 +01:00
9b12895fab admin: remove unneeded code 2021-02-19 19:32:46 +01:00
93478a55d7 web: migrate Stage List to web 2021-02-19 19:29:17 +01:00
a76cbf8b70 flows: separate flows api into smaller files 2021-02-19 19:14:16 +01:00
6597d5bd28 web: migrate Token List to web 2021-02-19 19:09:30 +01:00
fd28f37c0d web: migrate User list to web 2021-02-19 18:43:57 +01:00
d219f65e7a web: migrate System Task list to web 2021-02-19 18:12:23 +01:00
865f652476 web: migrate Outpost Service Connection to web 2021-02-19 17:49:34 +01:00
8008918d8b admin: remove PolicyBinding list 2021-02-19 17:37:19 +01:00
75d0bd01c2 admin: remove StageBinding list 2021-02-19 17:19:48 +01:00
029c6cd182 web: migrate Group list to web 2021-02-19 17:18:09 +01:00
71f771c22c core: add types API to propertymapping 2021-02-19 17:10:30 +01:00
0993d5ce4a web: reset retryDelay for WS connection after successful connect 2021-02-19 17:10:16 +01:00
38bd05867d web: migrate Policy list to web 2021-02-19 17:05:02 +01:00
79089d8981 policies: add bound count to api 2021-02-19 16:53:30 +01:00
44e51970e1 web: update for new cached actions 2021-02-19 16:37:50 +01:00
47bde052ca policies: add types action to policy API, use MetaNameSerializer 2021-02-19 16:34:33 +01:00
bd6a473d4f flows: add cached as action to flow API 2021-02-19 16:34:12 +01:00
cd23053007 web: fix height on table pages 2021-02-19 16:22:52 +01:00
6e11fd0f2e web: fix application library not being full height 2021-02-19 16:13:55 +01:00
277b4336d3 stages/authenticator_validate: update autocomplete for code input 2021-02-19 16:00:59 +01:00
1c1f9b6cb8 web: fix SiteShell not being full height 2021-02-19 15:34:21 +01:00
c23df5e1d5 build(deps): bump @sentry/browser from 6.1.0 to 6.2.0 in /web (#571)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.1.0...6.2.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 12:16:50 +01:00
c47cef6fbf build(deps): bump sentry-sdk from 0.20.2 to 0.20.3 (#567)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.20.2 to 0.20.3.
- [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/0.20.2...0.20.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:22:30 +01:00
83b7b3257a build(deps): bump boto3 from 1.17.10 to 1.17.11 (#568)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.10 to 1.17.11.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.10...1.17.11)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:22:16 +01:00
270be95e68 build(deps): bump structlog from 20.2.0 to 21.1.0 (#569)
Bumps [structlog](https://github.com/hynek/structlog) from 20.2.0 to 21.1.0.
- [Release notes](https://github.com/hynek/structlog/releases)
- [Changelog](https://github.com/hynek/structlog/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/hynek/structlog/compare/20.2.0...21.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:22:06 +01:00
1c919b8b88 build(deps): bump docker from 4.4.2 to 4.4.3 (#570)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.2...4.4.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:21:49 +01:00
1e51a2cdd7 build(deps): bump rollup-plugin-copy from 3.3.0 to 3.4.0 in /web (#572)
Bumps [rollup-plugin-copy](https://github.com/vladshcherbin/rollup-plugin-copy) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/vladshcherbin/rollup-plugin-copy/releases)
- [Commits](https://github.com/vladshcherbin/rollup-plugin-copy/compare/3.3.0...3.4.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:21:34 +01:00
7ba44b15a7 build(deps): bump @sentry/tracing from 6.1.0 to 6.2.0 in /web (#573)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.1.0...6.2.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-19 09:21:26 +01:00
4a94f515b3 root: add next branch 2021-02-18 21:24:34 +01:00
b229b2f40d Merge branch 'master' into stage-challenge
# Conflicts:
#	authentik/stages/authenticator_validate/stage.py
#	authentik/stages/identification/stage.py
2021-02-18 14:04:35 +01:00
e4f0613fab *: replace tuple and set from typing with normal 2021-02-18 13:53:57 +01:00
ecff810021 *: replace List from typing with normal list 2021-02-18 13:45:46 +01:00
fdde97cbbf *: replace Dict from typing with normal dict 2021-02-18 13:41:03 +01:00
c2a5641e6a Merge branch 'master' into stage-challenge 2021-02-18 12:46:28 +01:00
5a47c4850d build(deps): bump @patternfly/patternfly from 4.87.2 to 4.87.3 in /web (#566)
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 4.87.2 to 4.87.3.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/patternfly/patternfly/compare/prerelease-v4.87.2...prerelease-v4.87.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-18 09:12:31 +01:00
70b8a941bb build(deps): bump boto3 from 1.17.9 to 1.17.10 (#565)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.9 to 1.17.10.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.9...1.17.10)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-18 09:12:18 +01:00
eb01b42425 flows: mount executor under api, implement initial challenge design 2021-02-17 23:52:49 +01:00
8708e487ae stages: add WebAuthn stage (#550)
* core: add User.uid for globally unique user ID

* admin: fix ?next for Flow list

* stages: add initial webauthn implementation

* web: add ak-flow-submit event to submit flow stage

* web: show error message for webauthn registration

* admin: fix next param not redirecting correctly

* stages/webauthn: remove form

* stages/webauthn: add API

* web: update flow diagram on ak-refresh

* stages/webauthn: add initial authentication

* stages/webauthn: initial authentication implementation

* web: cleanup webauthn utils

* stages: rename otp_* to authenticator and move webauthn to authenticator

* docs: fix broken links

* stages/authenticator_*: fix template paths

* stages/authenticator_validate: add device classes

* stages/authenticator_webauthn: implement django_otp.devices

* stages/authenticator_*: update default stage names

* web: add button to create stage on flow page

* web: don't minify HTML, remove nbsp

* admin: fix typo in stage list

* stages/*: use common base class for stage serializer

* stages/authenticator_*: create default objects after rename

* tests/e2e: adjust stage order
2021-02-17 20:49:58 +01:00
e020b8bf32 build(deps): bump boto3 from 1.17.8 to 1.17.9 (#562)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.8 to 1.17.9.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.8...1.17.9)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-17 09:28:40 +01:00
8e27121e10 build(deps): bump @patternfly/patternfly from 4.80.3 to 4.87.2 in /web (#563)
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 4.80.3 to 4.87.2.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/patternfly/patternfly/compare/prerelease-v4.80.3...prerelease-v4.87.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-17 09:24:34 +01:00
06870b4f64 web: improve styling on model info 2021-02-16 23:19:01 +01:00
4cfcc48b23 admin: migrate certificate-keypair list to web 2021-02-16 23:16:52 +01:00
60c244c31d core: add User.uid for globally unique user ID 2021-02-16 23:04:48 +01:00
d122bddae2 web: update flow diagram on ak-refresh 2021-02-16 22:54:14 +01:00
69e6221906 web: port flow list 2021-02-16 22:52:54 +01:00
68eefd083e web: fix linting errors 2021-02-16 22:35:55 +01:00
a647917074 providers/saml: use redirect binding first 2021-02-16 21:35:19 +01:00
099197ba8c providers/saml: fix AuthnRequestsSigned and WantAssertionsSigned not loaded correctly 2021-02-16 21:30:15 +01:00
baa2ed5ecc web: fix download button for SAML Metadata download 2021-02-16 21:04:03 +01:00
f8ba623fc1 web: add more related links, add policy/user/group support for bindings 2021-02-16 20:52:59 +01:00
6bcdf36ca6 admin: add ?provider for ApplicationCreateView 2021-02-16 20:00:52 +01:00
416d949d80 web: add button to create application on provider page 2021-02-16 20:00:34 +01:00
0b75a0028b providers/saml: fix error when getting metadata of provider with no application 2021-02-16 19:58:04 +01:00
0901d7461e providers/saml: fix redirect error 2021-02-16 19:28:18 +01:00
61772b75ff providers/saml: fix managed mappings not being set on import 2021-02-16 19:20:52 +01:00
0ade57b5a6 docs: replace Autogenerated with managed mappings 2021-02-16 19:18:57 +01:00
61604adf9a root: fix request_id not being logged for actual asgi requests 2021-02-16 19:14:08 +01:00
8bd147b205 web: fix spacing on boudstagelist 2021-02-16 17:03:37 +01:00
724f53e972 build(deps): bump boto3 from 1.17.7 to 1.17.8 (#557)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.7 to 1.17.8.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.7...1.17.8)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 09:07:56 +00:00
c10478ec68 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#560)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.0 to 4.15.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.15.1/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 09:03:57 +00:00
cdf12ee03d build(deps): bump docker from 4.4.1 to 4.4.2 (#558)
Bumps [docker](https://github.com/docker/docker-py) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/docker/docker-py/releases)
- [Commits](https://github.com/docker/docker-py/compare/4.4.1...4.4.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 09:18:30 +01:00
964a8dbb82 build(deps): bump sentry-sdk from 0.20.1 to 0.20.2 (#559)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.20.1...0.20.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 09:17:21 +01:00
7ad48bfc44 build(deps-dev): bump @typescript-eslint/parser in /web (#561)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.15.0 to 4.15.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.15.1/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-16 09:17:07 +01:00
da90510b98 build(deps): bump rollup from 2.38.5 to 2.39.0 in /web (#553)
Bumps [rollup](https://github.com/rollup/rollup) from 2.38.5 to 2.39.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.38.5...v2.39.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:28:08 +01:00
4bd1598c2c build(deps): bump construct-style-sheets-polyfill in /web (#556)
Bumps [construct-style-sheets-polyfill](https://github.com/calebdwilliams/adoptedStyleSheets) from 2.4.9 to 2.4.16.
- [Release notes](https://github.com/calebdwilliams/adoptedStyleSheets/releases)
- [Changelog](https://github.com/calebdwilliams/construct-style-sheets/blob/master/CHANGELOG.md)
- [Commits](https://github.com/calebdwilliams/adoptedStyleSheets/compare/v2.4.9...v2.4.16)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:27:44 +01:00
6aa8d56d9f build(deps-dev): bump eslint from 7.19.0 to 7.20.0 in /web (#554)
Bumps [eslint](https://github.com/eslint/eslint) from 7.19.0 to 7.20.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.19.0...v7.20.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:27:31 +01:00
ccf7d794e9 build(deps): bump sentry-sdk from 0.20.0 to 0.20.1 (#552)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.20.0 to 0.20.1.
- [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/0.20.0...0.20.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:27:10 +01:00
50ed2fb257 build(deps): bump boto3 from 1.17.6 to 1.17.7 (#551)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.6 to 1.17.7.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.6...1.17.7)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:26:56 +01:00
5ae030997a providers/saml: fix missing import 2021-02-15 09:25:22 +01:00
52dabcaad9 build(deps-dev): bump @rollup/plugin-typescript in /web (#555)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins) from 8.1.1 to 8.2.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Commits](https://github.com/rollup/plugins/compare/typescript-v8.1.1...typescript-v8.2.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-15 09:05:01 +01:00
35e8a0c374 admin: fix ?next for Flow list 2021-02-14 18:39:36 +01:00
be292729a5 web: fix SiteShell breaking links when handlers are updated twice 2021-02-13 18:11:29 +01:00
1649c478b6 web: fix outpost edit/delete buttons 2021-02-12 11:56:54 +01:00
42feb54d80 helm: add initial wait for healthcheck 2021-02-12 11:53:49 +01:00
bbd088a957 events: fix geoip case when not configured 2021-02-12 10:43:00 +01:00
5417d0a90c *: bump pyright version 2021-02-12 10:19:38 +01:00
417b5d61a4 root: add initial geoip implementation 2021-02-12 09:52:14 +01:00
f13aad21cb build(deps): bump boto3 from 1.17.5 to 1.17.6 (#549)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.5 to 1.17.6.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.5...1.17.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-12 09:19:37 +01:00
79e8ee46c0 web: also use json logs for static parts 2021-02-12 00:00:11 +01:00
e3eaaeaf17 outpost: improve logging output, ensure fields match api server 2021-02-11 23:52:00 +01:00
e550216f85 events: fix user QuerySet being passed 2021-02-11 23:36:22 +01:00
1afb4a7a76 policies: add ability to directly assign groups in bindings 2021-02-11 20:36:48 +01:00
391eb9d469 admin: fix policy list not having a refresh button 2021-02-11 20:17:45 +01:00
494f094fa1 events: pass Event's user to Notification policy engine when present 2021-02-11 19:56:39 +01:00
aa0f5df218 policies/*: cleanup api and forms, use correct inheritance 2021-02-11 19:50:02 +01:00
6fc740a98b build(deps-dev): bump typescript from 4.1.4 to 4.1.5 in /web (#547)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.1.4...v4.1.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-11 09:12:09 +01:00
522 changed files with 12524 additions and 6914 deletions

View File

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

View File

@ -1,7 +1,7 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/proxy"
directory: "/outpost"
schedule:
interval: daily
time: "04:00"
@ -41,7 +41,7 @@ updates:
assignees:
- BeryJu
- package-ecosystem: docker
directory: "/proxy"
directory: "/outpost"
schedule:
interval: daily
time: "04:00"

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/authentik:2021.2.6-stable
-t beryju/authentik:2021.3.1-rc1
-t beryju/authentik:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:2021.2.6-stable
run: docker push beryju/authentik:2021.3.1-rc1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest
build-proxy:
@ -48,11 +48,11 @@ jobs:
cd outpost/
docker build \
--no-cache \
-t beryju/authentik-proxy:2021.2.6-stable \
-t beryju/authentik-proxy:2021.3.1-rc1 \
-t beryju/authentik-proxy:latest \
-f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:2021.2.6-stable
run: docker push beryju/authentik-proxy:2021.3.1-rc1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest
build-static:
@ -69,11 +69,11 @@ jobs:
cd web/
docker build \
--no-cache \
-t beryju/authentik-static:2021.2.6-stable \
-t beryju/authentik-static:2021.3.1-rc1 \
-t beryju/authentik-static:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:2021.2.6-stable
run: docker push beryju/authentik-static:2021.3.1-rc1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest
test-release:
@ -107,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 2021.2.6-stable
tagName: 2021.3.1-rc1
environment: beryjuorg-prod

1
.gitignore vendored
View File

@ -201,3 +201,4 @@ local.env.yml
selenium_screenshots/
backups/
media/
*mmdb

View File

@ -20,7 +20,7 @@ RUN apt-get update && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config && \
apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config libmaxminddb0 && \
apt-get clean && \
pip install -r /requirements.txt --no-cache-dir && \
apt-get remove --purge -y build-essential && \

View File

@ -1,20 +1,15 @@
all: lint-fix lint coverage gen
test-full:
coverage run manage.py test --failfast -v 3 .
coverage html
coverage report
test-integration:
k3d cluster create || exit 0
k3d kubeconfig write -o ~/.kube/config --overwrite
coverage run manage.py test --failfast -v 3 tests/integration
coverage run manage.py test -v 3 tests/integration
test-e2e:
coverage run manage.py test --failfast -v 3 tests/e2e
coverage run manage.py test -v 3 tests/e2e
coverage:
coverage run manage.py test --failfast -v 3 authentik
coverage run manage.py test -v 3 authentik
coverage html
coverage report

24
Pipfile
View File

@ -6,6 +6,9 @@ verify_ssl = true
[packages]
boto3 = "*"
celery = "*"
channels = "*"
channels-redis = "*"
dacite = "*"
defusedxml = "*"
django = "*"
django-cors-middleware = "*"
@ -15,35 +18,33 @@ django-guardian = "*"
django-model-utils = "*"
django-otp = "*"
django-prometheus = "*"
django-recaptcha = "*"
django-redis = "*"
djangorestframework = "*"
django-storages = "*"
djangorestframework = "*"
djangorestframework-guardian = "*"
docker = "*"
drf_yasg2 = "*"
facebook-sdk = "*"
geoip2 = "*"
gunicorn = "*"
kubernetes = "*"
ldap3 = "*"
lxml = "*"
packaging = "*"
psycopg2-binary = "*"
pycryptodome = "*"
pyjwkest = "*"
uvicorn = {extras = ["standard"],version = "*"}
gunicorn = "*"
pyyaml = "*"
qrcode = "*"
requests-oauthlib = "*"
sentry-sdk = "*"
service_identity = "*"
structlog = "*"
swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"}
dacite = "*"
channels = "*"
channels-redis = "*"
kubernetes = "*"
docker = "*"
uvicorn = {extras = ["standard"],version = "*"}
webauthn = "*"
xmlsec = "*"
twisted = "==20.3.0"
[requires]
python_version = "3.9"
@ -55,8 +56,7 @@ black = "==20.8b1"
bumpversion = "*"
colorama = "*"
coverage = "*"
django-debug-toolbar = "*"
pylint = "*"
pylint = "<=2.6.0"
pylint-django = "*"
selenium = "*"
prospector = "*"

533
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "53ad00e394a5f2e2462837c4ceff837d1e593469af9505726048bed72ce0b81a"
"sha256": "d2d51b999d57d01e1f8e6450ae54f8e58e1eb213ae457cc43f7314daa8e11541"
},
"pipfile-spec": 6,
"requires": {
@ -16,6 +16,48 @@
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0",
"sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6",
"sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf",
"sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9",
"sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e",
"sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0",
"sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329",
"sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2",
"sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40",
"sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a",
"sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4",
"sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de",
"sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9",
"sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9",
"sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb",
"sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076",
"sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de",
"sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907",
"sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d",
"sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536",
"sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d",
"sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54",
"sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc",
"sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212",
"sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9",
"sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d",
"sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b",
"sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7",
"sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81",
"sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c",
"sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895",
"sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297",
"sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb",
"sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe",
"sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242",
"sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0",
"sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"
],
"version": "==3.7.4"
},
"aioredis": {
"hashes": [
"sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
@ -53,10 +95,10 @@
},
"autobahn": {
"hashes": [
"sha256:93df8fc9d1821c9dabff9fed52181a9ad6eea5e9989d53102c391607d7c1666e",
"sha256:cceed2121b7a93024daa93c91fae33007f8346f0e522796421f36a6183abea99"
"sha256:884f79c50fdc55ade2c315946a9caa145e8b10075eee9d2c2594ea5e8f5226aa",
"sha256:bf7a9d302a34d0f719d43c57f65ca1f2f5c982dd6ea0c11e1e190ef6f43710fe"
],
"version": "==21.1.1"
"version": "==21.2.2"
},
"automat": {
"hashes": [
@ -74,17 +116,18 @@
},
"boto3": {
"hashes": [
"sha256:d6aafb804fca2b67c65dda78ad8b4afed901e004071208b84c804d345ad9ebba"
"sha256:3570a3c0fbd80bcb30449f87cf9d2f7abb67fac2a5e317d002f9921c59be9b17",
"sha256:ceff2f32ba05acc9ee35a6dd82e29ea285d63e889bed39a6ba7a700146f43749"
],
"index": "pypi",
"version": "==1.17.5"
"version": "==1.17.18"
},
"botocore": {
"hashes": [
"sha256:04a1df759681f5f171accb354d863bfed0774d64a4e8ee35ff49835755660a4e",
"sha256:3c55f0db5e08920727f4fa24a87aed60060643f4b0b5665c62ec762f79e82d6b"
"sha256:51900b10da4ae45be4b16045e5b2ff7d1158a7955d9d7cc5e5a9ba3170f10586",
"sha256:b181f32d9075e5419a89fa9636ce95946c15459c9bfadfabb53ca902fc8072b8"
],
"version": "==1.20.5"
"version": "==1.20.18"
},
"cachetools": {
"hashes": [
@ -93,6 +136,12 @@
],
"version": "==4.2.1"
},
"cbor2": {
"hashes": [
"sha256:a33aa2e5534fd74401ac95686886e655e3b2ce6383b3f958199b6e70a87c94bf"
],
"version": "==5.2.0"
},
"celery": {
"hashes": [
"sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13",
@ -110,45 +159,45 @@
},
"cffi": {
"hashes": [
"sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e",
"sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d",
"sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a",
"sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec",
"sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362",
"sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668",
"sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c",
"sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b",
"sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06",
"sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698",
"sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2",
"sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c",
"sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7",
"sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009",
"sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03",
"sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b",
"sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e",
"sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909",
"sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53",
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
"sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26",
"sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b",
"sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01",
"sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb",
"sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293",
"sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd",
"sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d",
"sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3",
"sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d",
"sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e",
"sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca",
"sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d",
"sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775",
"sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375",
"sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b",
"sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b",
"sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
],
"version": "==1.14.4"
"version": "==1.14.5"
},
"channels": {
"hashes": [
@ -168,10 +217,10 @@
},
"chardet": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==4.0.0"
"version": "==3.0.4"
},
"click": {
"hashes": [
@ -223,15 +272,20 @@
},
"cryptography": {
"hashes": [
"sha256:287032b6a7d86abc98e8e977b20138c53fea40e5b24e29090d5a675a973dcd10",
"sha256:288c65eea20bd89b11102c47b118bc1e0749386b0a0dfebba414076c5d4c8188",
"sha256:7eed937ad9b53280a5f53570d3a7dc93cb4412b6a3d58d4c6bb78cc26319c729",
"sha256:dab437c2e84628703e3358f0f06555a6259bc5039209d51aa3b05af667ff4fd0",
"sha256:ee5e19f0856b6fbbdbab15c2787ca65d203801d2d65d0b8de6218f424206c848",
"sha256:f21be9ec6b44c223b2024bbe59d394fadc7be320d18a8d595419afadb6cd5620",
"sha256:f6ea140d2736b7e1f0de4f988c43f76b0b3f3d365080e091715429ba218dce28"
"sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b",
"sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336",
"sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87",
"sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7",
"sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799",
"sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b",
"sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df",
"sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0",
"sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3",
"sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724",
"sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2",
"sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"
],
"version": "==3.4.4"
"version": "==3.4.6"
},
"dacite": {
"hashes": [
@ -258,11 +312,11 @@
},
"django": {
"hashes": [
"sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
"sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
"sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7",
"sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8"
],
"index": "pypi",
"version": "==3.1.6"
"version": "==3.1.7"
},
"django-cors-middleware": {
"hashes": [
@ -319,13 +373,6 @@
"index": "pypi",
"version": "==2.1.0"
},
"django-recaptcha": {
"hashes": [
"sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c"
],
"index": "pypi",
"version": "==2.0.6"
},
"django-redis": {
"hashes": [
"sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5",
@ -360,11 +407,11 @@
},
"docker": {
"hashes": [
"sha256:0604a74719d5d2de438753934b755bfcda6f62f49b8e4b30969a4b0a2a8a1220",
"sha256:e455fa49aabd4f22da9f4e1c1f9d16308286adc60abaf64bf3e1feafaed81d06"
"sha256:d3393c878f575d3a9ca3b94471a3c89a6d960b35feb92f033c0de36cc9d934db",
"sha256:f3607d5695be025fa405a12aca2e5df702a57db63790c73b927eb6a94aac60af"
],
"index": "pypi",
"version": "==4.4.1"
"version": "==4.4.4"
},
"drf-yasg2": {
"hashes": [
@ -388,12 +435,20 @@
],
"version": "==0.18.2"
},
"geoip2": {
"hashes": [
"sha256:57d8d15de2527e0697bbef44fc16812bba709f03a07ef99297bd56c1df3b1efd",
"sha256:707025542ef076bd8fd80e97138bebdb7812527b2a007d141a27ad98b0370fff"
],
"index": "pypi",
"version": "==4.1.0"
},
"google-auth": {
"hashes": [
"sha256:008e23ed080674f69f9d2d7d80db4c2591b9bb307d136cea7b3bc129771d211d",
"sha256:514e39f4190ca972200ba33876da5a8857c5665f2b4ccc36c8b8ee21228aae80"
"sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65",
"sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e"
],
"version": "==1.25.0"
"version": "==1.27.0"
},
"gunicorn": {
"hashes": [
@ -494,10 +549,10 @@
},
"incremental": {
"hashes": [
"sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f",
"sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"
"sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
"sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
],
"version": "==17.5.0"
"version": "==21.3.0"
},
"inflection": {
"hashes": [
@ -657,6 +712,12 @@
],
"version": "==1.1.1"
},
"maxminddb": {
"hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
],
"version": "==2.0.3"
},
"msgpack": {
"hashes": [
"sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9",
@ -690,6 +751,48 @@
],
"version": "==1.0.2"
},
"multidict": {
"hashes": [
"sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a",
"sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93",
"sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632",
"sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656",
"sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79",
"sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7",
"sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d",
"sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5",
"sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224",
"sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26",
"sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea",
"sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348",
"sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6",
"sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76",
"sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1",
"sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f",
"sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952",
"sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a",
"sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37",
"sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9",
"sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359",
"sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8",
"sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da",
"sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3",
"sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d",
"sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf",
"sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841",
"sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d",
"sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93",
"sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f",
"sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647",
"sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635",
"sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456",
"sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda",
"sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5",
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
],
"version": "==5.1.0"
},
"oauthlib": {
"hashes": [
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
@ -714,10 +817,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
],
"version": "==3.0.14"
"version": "==3.0.16"
},
"psycopg2-binary": {
"hashes": [
@ -934,14 +1037,6 @@
"index": "pypi",
"version": "==5.4.1"
},
"qrcode": {
"hashes": [
"sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5",
"sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"
],
"index": "pypi",
"version": "==6.1"
},
"redis": {
"hashes": [
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
@ -966,11 +1061,11 @@
},
"rsa": {
"hashes": [
"sha256:69805d6b69f56eb05b62daea3a7dbd7aa44324ad1306445e05da8060232d00f4",
"sha256:a8774e55b59fd9fc893b0d05e9bfc6f47081f46ff5b46f39ccf24631b7be356b"
"sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2",
"sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"
],
"markers": "python_version >= '3.6'",
"version": "==4.7"
"version": "==4.7.2"
},
"ruamel.yaml": {
"hashes": [
@ -988,11 +1083,11 @@
},
"sentry-sdk": {
"hashes": [
"sha256:0a711ec952441c2ec89b8f5d226c33bc697914f46e876b44a4edd3e7864cf4d0",
"sha256:737a094e49a529dd0fdcaafa9e97cf7c3d5eb964bd229821d640bc77f3502b3f"
"sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237",
"sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b"
],
"index": "pypi",
"version": "==0.19.5"
"version": "==0.20.3"
},
"service-identity": {
"hashes": [
@ -1018,11 +1113,11 @@
},
"structlog": {
"hashes": [
"sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd",
"sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174"
"sha256:62f06fc0ee32fb8580f0715eea66cb87271eb7efb0eaf9af6b639cba8981de47",
"sha256:d9d2d890532e8db83c6977a2a676fb1889922ff0c26ad4dc0ecac26f9fafbc57"
],
"index": "pypi",
"version": "==20.2.0"
"version": "==21.1.0"
},
"swagger-spec-validator": {
"hashes": [
@ -1061,14 +1156,23 @@
"sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467",
"sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4"
],
"index": "pypi",
"version": "==20.3.0"
},
"txaio": {
"hashes": [
"sha256:1488d31d564a116538cc1265ac3f7979fb6223bb5a9e9f1479436ee2c17d8549",
"sha256:a8676d6c68aea1f0e2548c4afdb8e6253873af3bc2659bb5bcd9f39dff7ff90f"
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
],
"version": "==20.12.1"
"version": "==21.2.1"
},
"typing-extensions": {
"hashes": [
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
],
"version": "==3.7.4.3"
},
"uritemplate": {
"hashes": [
@ -1093,25 +1197,26 @@
"standard"
],
"hashes": [
"sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c",
"sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"
"sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202",
"sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"
],
"index": "pypi",
"version": "==0.13.3"
"version": "==0.13.4"
},
"uvloop": {
"hashes": [
"sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd",
"sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e",
"sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09",
"sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726",
"sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891",
"sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7",
"sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5",
"sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95",
"sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"
"sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc",
"sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69",
"sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01",
"sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d",
"sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760",
"sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c",
"sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47",
"sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c",
"sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c",
"sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"
],
"version": "==0.14.0"
"version": "==0.15.2"
},
"vine": {
"hashes": [
@ -1122,10 +1227,10 @@
},
"watchgod": {
"hashes": [
"sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a",
"sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"
"sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29",
"sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"
],
"version": "==0.6"
"version": "==0.7"
},
"wcwidth": {
"hashes": [
@ -1134,6 +1239,14 @@
],
"version": "==0.2.5"
},
"webauthn": {
"hashes": [
"sha256:238391b2e2cc60fb51a2cd2d2d6be149920b9af6184651353d9f95856617a9e7",
"sha256:8ad9072ff1d6169f3be30d4dc8733ea563dd266962397bc58b40f674a6af74ac"
],
"index": "pypi",
"version": "==0.4.7"
},
"websocket-client": {
"hashes": [
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
@ -1187,6 +1300,48 @@
"index": "pypi",
"version": "==1.3.9"
},
"yarl": {
"hashes": [
"sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e",
"sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434",
"sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366",
"sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3",
"sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec",
"sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959",
"sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e",
"sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c",
"sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6",
"sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a",
"sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6",
"sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424",
"sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e",
"sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f",
"sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50",
"sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2",
"sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc",
"sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4",
"sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970",
"sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10",
"sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0",
"sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406",
"sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896",
"sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643",
"sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721",
"sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478",
"sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724",
"sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e",
"sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8",
"sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96",
"sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25",
"sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76",
"sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2",
"sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2",
"sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c",
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
],
"version": "==1.6.3"
},
"zope.interface": {
"hashes": [
"sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1",
@ -1253,13 +1408,6 @@
],
"version": "==1.4.4"
},
"asgiref": {
"hashes": [
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
],
"version": "==3.3.1"
},
"astroid": {
"hashes": [
"sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1",
@ -1329,74 +1477,61 @@
},
"coverage": {
"hashes": [
"sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7",
"sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5",
"sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f",
"sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde",
"sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f",
"sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f",
"sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c",
"sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66",
"sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90",
"sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337",
"sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d",
"sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4",
"sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409",
"sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37",
"sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1",
"sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247",
"sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39",
"sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c",
"sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994",
"sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c",
"sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb",
"sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc",
"sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f",
"sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca",
"sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135",
"sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3",
"sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339",
"sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9",
"sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9",
"sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af",
"sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370",
"sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19",
"sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3",
"sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44",
"sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3",
"sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a",
"sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c",
"sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b",
"sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9",
"sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8",
"sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22",
"sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f",
"sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345",
"sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880",
"sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0",
"sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b",
"sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec",
"sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3",
"sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
],
"index": "pypi",
"version": "==5.4"
},
"django": {
"hashes": [
"sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
"sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
],
"index": "pypi",
"version": "==3.1.6"
},
"django-debug-toolbar": {
"hashes": [
"sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2",
"sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a"
],
"index": "pypi",
"version": "==3.2"
"version": "==5.5"
},
"dodgy": {
"hashes": [
@ -1428,10 +1563,10 @@
},
"gitpython": {
"hashes": [
"sha256:8621a7e777e276a5ec838b59280ba5272dd144a18169c36c903d8b38b99f750a",
"sha256:c5347c81d232d9b8e7f47b68a83e5dc92e7952127133c5f2df9133f2c75a1b29"
"sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b",
"sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61"
],
"version": "==3.1.13"
"version": "==3.1.14"
},
"iniconfig": {
"hashes": [
@ -1616,13 +1751,6 @@
"index": "pypi",
"version": "==4.1.0"
},
"pytz": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
],
"version": "==2021.1"
},
"pyyaml": {
"hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
@ -1737,13 +1865,6 @@
],
"version": "==2.1.0"
},
"sqlparse": {
"hashes": [
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
],
"version": "==0.4.1"
},
"stevedore": {
"hashes": [
"sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee",

View File

@ -2,6 +2,7 @@
---
[![](https://img.shields.io/discord/809154715984199690?label=Discord&style=flat-square)](https://discord.gg/KPnmtNWy)
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/1?style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
[![Tests](https://img.shields.io/azure-devops/tests/beryjuorg/authentik/1?compact_message&style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
[![Code Coverage](https://img.shields.io/codecov/c/gh/beryju/authentik?style=flat-square)](https://codecov.io/gh/BeryJu/authentik)

View File

@ -1,2 +1,2 @@
"""authentik"""
__version__ = "2021.2.6-stable"
__version__ = "2021.3.1-rc1"

View File

@ -2,7 +2,6 @@
import time
from collections import Counter
from datetime import timedelta
from typing import Dict, List
from django.db.models import Count, ExpressionWrapper, F, Model
from django.db.models.fields import DurationField
@ -19,7 +18,7 @@ from rest_framework.viewsets import ViewSet
from authentik.events.models import Event, EventAction
def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]:
def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
"""Get event count by hour in the last day, fill with zeros"""
date_from = now() - timedelta(days=1)
result = (
@ -32,7 +31,7 @@ def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]:
.annotate(count=Count("pk"))
.order_by("age_hours")
)
data = Counter({d["age_hours"]: d["count"] for d in result})
data = Counter({int(d["age_hours"]): d["count"] for d in result})
results = []
_now = now()
for hour in range(0, -24, -1):

View File

@ -1,122 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-key"></i>
{% trans 'Certificate-Key Pairs' %}
</h1>
<p>{% trans "Import certificates of external providers or create certificates to sign requests with." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:certificatekeypair-generate' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Generate' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Private Key available' %}</th>
<th role="columnheader" scope="col">{% trans 'Fingerprint' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for kp in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ kp.name }}</div>
</div>
</th>
<td role="cell">
<span>
{% if kp.key_data is not None %}
{% trans 'Yes' %}
{% else %}
{% trans 'No' %}
{% endif %}
</span>
</td>
<td role="cell">
<code>{{ kp.fingerprint }}</code>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:certificatekeypair-update' pk=kp.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:certificatekeypair-delete' pk=kp.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-key pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Certificates.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any certificates." %}
{% else %}
{% trans 'Currently no certificates exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,135 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-process-automation"></i>
{% trans 'Flows' %}
</h1>
<p>{% trans "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:flow-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:flow-import' %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Import' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'Designation' %}</th>
<th role="columnheader" scope="col">{% trans 'Stages' %}</th>
<th role="columnheader" scope="col">{% trans 'Policies' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for flow in object_list %}
<tr role="row">
<th role="columnheader">
<a href="/flows/{{ flow.slug }}">
<div><code>{{ flow.slug }}</code></div>
<small>{{ flow.name }}</small>
</a>
</th>
<td role="cell">
<span>
{{ flow.designation }}
</span>
</td>
<td role="cell">
<span>
{{ flow.stages.all|length }}
</span>
</td>
<td role="cell">
<span>
{{ flow.policies.all|length }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:flow-update' pk=flow.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:flow-delete' pk=flow.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a>
<a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-process-automation pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Flows.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any flows." %}
{% else %}
{% trans 'Currently no flows exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:flow-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:flow-import' %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Import' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,114 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-users"></i>
{% trans 'Groups' %}
</h1>
<p>{% trans "Group users together and give them permissions based on the membership." %}
</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:group-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Parent' %}</th>
<th role="columnheader" scope="col">{% trans 'Members' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for group in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ group.name }}
</span>
</td>
<td role="cell">
<span>
{{ group.parent }}
</span>
</td>
<td role="cell">
<span>
{{ group.users.all|length }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:group-update' pk=group.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:group-delete' pk=group.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-users pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Groups.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any groups." %}
{% else %}
{% trans 'Currently no group exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:group-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,153 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load humanize %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon-integration"></i>
{% trans 'Outpost Service-Connections' %}
</h1>
<p>{% trans "Outpost Service-Connections define how authentik connects to external platforms to manage and deploy Outposts." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Local?' %}</th>
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for sc in object_list %}
<tr role="row">
<th role="columnheader">
<span>{{ sc.name }}</span>
</th>
<td role="cell">
<span>
{{ sc|verbose_name }}
</span>
</td>
<td role="cell">
<span>
{{ sc.local|yesno:"Yes,No" }}
</span>
</td>
<td role="cell">
<span>
{% if sc.state.healthy %}
<i class="fas fa-check pf-m-success"></i> {{ sc.state.version }}
{% else %}
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
{% endif %}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-update' pk=sc.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-delete' pk=sc.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Outpost Service Connections.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any outposts." %}
{% else %}
{% trans 'Currently no service connections exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,151 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Policies' %}
</h1>
<p>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for policy in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ policy.name }}</div>
{% if not policy.bindings.exists and not policy.promptstage_set.exists %}
<i class="pf-icon pf-icon-warning-triangle"></i>
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
{% else %}
<i class="pf-icon pf-icon-ok"></i>
<small>{% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small>
{% endif %}
</div>
</th>
<td role="cell">
<span>
{{ policy|verbose_name }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:policy-update' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-test' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Test' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-delete' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Policies.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any policies." %}
{% else %}
{% trans 'Currently no policies exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,119 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Policy Bindings' %}
</h1>
<p>{% trans "Bind existing Policies to Models accepting policies." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Policy' %}</th>
<th role="columnheader" scope="col">{% trans 'Enabled' %}</th>
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Timeout' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for pbm in object_list %}
<tr role="role">
<td>
{{ pbm }}
<small>
{{ pbm|fieldtype }}
</small>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{% for binding in pbm.bindings %}
<tr class="row pf-c-table__expandable-row pf-m-expanded">
<th role="cell">
<div>{{ binding.policy }}</div>
<small>
{{ binding.policy|fieldtype }}
</small>
</th>
<th role="cell">
<div>{{ binding.enabled }}</div>
</th>
<th role="cell">
<div>{{ binding.order }}</div>
</th>
<th role="cell">
<div>{{ binding.timeout }}</div>
</th>
<td>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-update' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-delete' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Policy Bindings.' %}
</h1>
<div class="pf-c-empty-state__body">
{% trans 'Currently no policy bindings exist. Click the button below to create one.' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,28 +0,0 @@
{% extends 'generic/form.html' %}
{% load i18n %}
{% block above_form %}
<h1>{% blocktrans with property_mapping=property_mapping %}Test {{ property_mapping }}{% endblocktrans %}</h1>
{% endblock %}
{% block beneath_form %}
{% if result %}
<div class="pf-c-form__group ">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="context-1">
<span class="pf-c-form__label-text">{% trans 'Result' %}</span>
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
<ak-codemirror mode="javascript"><textarea class="pf-c-form-control">{{ result }}</textarea></ak-codemirror>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block action %}
{% trans 'Test' %}
{% endblock %}

View File

@ -1,148 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-middleware"></i>
{% trans 'Source' %}
</h1>
<p>{% trans "External Sources which can be used to get Identities into authentik, for example Social Providers like Twiter and GitHub or Enterprise Providers like ADFS and LDAP." %}
</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Additional Info' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for source in object_list %}
<tr role="row">
<th role="columnheader">
<a href="/sources/{{ source.slug }}">
<div>{{ source.name }}</div>
{% if not source.enabled %}
<small>{% trans 'Disabled' %}</small>
{% endif %}
</a>
</th>
<td role="cell">
<span>
{{ source|fieldtype }}
</span>
</td>
<td role="cell">
<span>
{{ source.ui_additional_info|default:""|safe }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:source-update' pk=source.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:source-delete' pk=source.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-middleware pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Sources.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any sources." %}
{% else %}
{% trans 'Currently no sources exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,143 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-plugged"></i>
{% trans 'Stages' %}
</h1>
<p>{% trans "Stages are single steps of a Flow that a user is guided through." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Flows' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for stage in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ stage.name }}</div>
<small>{{ stage|verbose_name }}</small>
</div>
</th>
<td role="cell">
<ul>
{% for flow in stage.flow_set.all %}
<li>{{ flow.slug }}<</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-update' pk=stage.stage_uuid %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-delete' pk=stage.stage_uuid %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Stages.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any stages." %}
{% else %}
{% trans 'Currently no stages exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-infrastructure"></i>
{% trans 'Stage Bindings' %}
</h1>
<p>{% trans "Bind existing Stages to Flows." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Stage Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% regroup object_list by target as grouped_bindings %}
{% for flow in grouped_bindings %}
<tr role="role">
<td>
{% blocktrans with slug=flow.grouper.slug %}
Flow {{ slug }}
{% endblocktrans %}
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% for binding in flow.list %}
<tr class="pf-c-table__expandable-row pf-m-expanded" role="row">
<td role="cell">
<span>
{{ binding.order }}
</span>
</td>
<th role="columnheader">
<div>
<div>{{ binding.target.slug }}</div>
<small>
{{ binding.target.name }}
</small>
</div>
</th>
<td role="cell">
<div>
<div>
{{ binding.stage.name }}
</div>
<small>
{{ binding.stage }}
</small>
</div>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-update' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-delete' pk=binding.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Flow-Stage Bindings.' %}
</h1>
<div class="pf-c-empty-state__body">
{% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,109 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-migration"></i>
{% trans 'Invitations' %}
</h1>
<p>{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %}
</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'ID' %}</th>
<th role="columnheader" scope="col">{% trans 'Created by' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for invitation in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ invitation.invite_uuid }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.created_by }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.expiry|default:"-" }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-delete' pk=invitation.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Invitations.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any invitations." %}
{% else %}
{% trans 'Currently no invitations exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-plugged"></i>
{% trans 'Prompts' %}
</h1>
<p>{% trans "Single Prompts that can be used for Prompt Stages." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Field' %}</th>
<th role="columnheader" scope="col">{% trans 'Label' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Flows' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for prompt in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ prompt.field_key }}</div>
</div>
</th>
<td role="cell">
<div>
{{ prompt.label }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.type }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.order }}
</div>
</td>
<td role="cell">
<ul>
{% for flow in prompt.flow_set.all %}
<li>{{ flow.slug }}</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-update' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-delete' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Stage Prompts.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any stage prompts." %}
{% else %}
{% trans 'Currently no stage prompts exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'authentik_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,84 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load humanize %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-automation"></i>
{% trans 'System Tasks' %}
</h1>
<p>{% trans "Long-running operations which authentik executes in the background." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'Description' %}</th>
<th role="columnheader" scope="col">{% trans 'Last Run' %}</th>
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
<th role="columnheader" scope="col">{% trans 'Messages' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for task in object_list %}
<tr role="row">
<th role="columnheader">
<span>{{ task.html_name|join:"_&shy;" }}</span>
</th>
<td role="cell">
<span>
{{ task.task_description }}
</span>
</td>
<td role="cell">
<span>
{{ task.finish_timestamp|naturaltime }}
</span>
</td>
<td role="cell">
<span>
{% if task.result.status == task_successful %}
<i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %}
{% elif task.result.status == task_warning %}
<i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %}
{% elif task.result.status == task_error %}
<i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %}
{% else %}
<i class="fas fa-question-circle"></i> {% trans 'Unknown' %}
{% endif %}
</span>
</td>
<td>
{% for message in task.result.messages %}
<div>
{{ message }}
</div>
{% endfor %}
</td>
<td>
<ak-action-button url="{% url 'authentik_api:admin_system_tasks-retry' pk=task.task_name %}">
{% trans 'Retry Task' %}
</ak-action-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View File

@ -1,102 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-security"></i>
{% trans 'Tokens' %}
</h1>
<p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for token in object_list %}
<tr role="row">
<th role="columnheader">
<div>{{ token.identifier }}</div>
</th>
<td role="cell">
<span>
{{ token.user }}
</span>
</td>
<td role="cell">
<span>
{{ token.expiring|yesno:"Yes,No" }}
</span>
</td>
<td role="cell">
<span>
{% if not token.expiring %}
-
{% else %}
{{ token.expires }}
{% endif %}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-token-copy-button identifier="{{ token.identifier }}">
{% trans 'Copy token' %}
</ak-token-copy-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Tokens.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any token." %}
{% else %}
{% trans 'Currently no tokens exist.' %}
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,125 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-user"></i>
{% trans 'Users' %}
</h1>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:user-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Active' %}</th>
<th role="columnheader" scope="col">{% trans 'Last Login' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for user in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ user.username }}</div>
<small>{{ user.name }}</small>
</div>
</th>
<td role="cell">
<span>
{{ user.is_active }}
</span>
</td>
<td role="cell">
<span>
{{ user.last_login }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:user-update' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% if user.is_active %}
<ak-modal-button href="{% url 'authentik_admin:user-disable' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-warning">
{% trans 'Disable' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% else %}
<ak-modal-button href="{% url 'authentik_admin:user-delete' pk=user.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Enable' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
{% endif %}
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
<a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
</div>
</div>
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="pf-icon pf-icon-user pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Users.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any users." %}
{% else %}
{% trans 'Currently no users exist. How did you even get here.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:user-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,8 +1,8 @@
"""test admin api"""
from json import loads
from django.shortcuts import reverse
from django.test import TestCase
from django.urls import reverse
from authentik import __version__
from authentik.core.models import Group, User

View File

@ -3,8 +3,8 @@ from importlib import import_module
from typing import Callable
from django.forms import ModelForm
from django.shortcuts import reverse
from django.test import Client, TestCase
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from authentik.admin.urls import urlpatterns

View File

@ -20,7 +20,6 @@ from authentik.admin.views import (
stages_bindings,
stages_invitations,
stages_prompts,
tasks,
tokens,
users,
)
@ -54,7 +53,6 @@ urlpatterns = [
name="application-delete",
),
# Tokens
path("tokens/", tokens.TokenListView.as_view(), name="tokens"),
path(
"tokens/<uuid:pk>/delete/",
tokens.TokenDeleteView.as_view(),
@ -73,7 +71,6 @@ urlpatterns = [
name="source-delete",
),
# Policies
path("policies/", policies.PolicyListView.as_view(), name="policies"),
path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"),
path(
"policies/<uuid:pk>/update/",
@ -91,11 +88,6 @@ urlpatterns = [
name="policy-test",
),
# Policy bindings
path(
"policies/bindings/",
policies_bindings.PolicyBindingListView.as_view(),
name="policies-bindings",
),
path(
"policies/bindings/create/",
policies_bindings.PolicyBindingCreateView.as_view(),
@ -133,7 +125,6 @@ urlpatterns = [
name="provider-delete",
),
# Stages
path("stages/", stages.StageListView.as_view(), name="stages"),
path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"),
path(
"stages/<uuid:pk>/update/",
@ -146,11 +137,6 @@ urlpatterns = [
name="stage-delete",
),
# Stage bindings
path(
"stages/bindings/",
stages_bindings.StageBindingListView.as_view(),
name="stage-bindings",
),
path(
"stages/bindings/create/",
stages_bindings.StageBindingCreateView.as_view(),
@ -167,11 +153,6 @@ urlpatterns = [
name="stage-binding-delete",
),
# Stage Prompts
path(
"stages_prompts/",
stages_prompts.PromptListView.as_view(),
name="stage-prompts",
),
path(
"stages_prompts/create/",
stages_prompts.PromptCreateView.as_view(),
@ -188,11 +169,6 @@ urlpatterns = [
name="stage-prompt-delete",
),
# Stage Invitations
path(
"stages/invitations/",
stages_invitations.InvitationListView.as_view(),
name="stage-invitations",
),
path(
"stages/invitations/create/",
stages_invitations.InvitationCreateView.as_view(),
@ -204,7 +180,6 @@ urlpatterns = [
name="stage-invitation-delete",
),
# Flows
path("flows/", flows.FlowListView.as_view(), name="flows"),
path(
"flows/create/",
flows.FlowCreateView.as_view(),
@ -257,7 +232,6 @@ urlpatterns = [
name="property-mapping-test",
),
# Users
path("users/", users.UserListView.as_view(), name="users"),
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
@ -271,7 +245,6 @@ urlpatterns = [
name="user-password-reset",
),
# Groups
path("groups/", groups.GroupListView.as_view(), name="groups"),
path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"),
path(
"groups/<uuid:pk>/update/",
@ -284,11 +257,6 @@ urlpatterns = [
name="group-delete",
),
# Certificate-Key Pairs
path(
"crypto/certificates/",
certificate_key_pair.CertificateKeyPairListView.as_view(),
name="certificate_key_pair",
),
path(
"crypto/certificates/create/",
certificate_key_pair.CertificateKeyPairCreateView.as_view(),
@ -326,11 +294,6 @@ urlpatterns = [
name="outpost-delete",
),
# Outpost Service Connections
path(
"outpost_service_connections/",
outposts_service_connections.OutpostServiceConnectionListView.as_view(),
name="outpost-service-connections",
),
path(
"outpost_service_connections/create/",
outposts_service_connections.OutpostServiceConnectionCreateView.as_view(),
@ -346,12 +309,6 @@ urlpatterns = [
outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(),
name="outpost-service-connection-delete",
),
# Tasks
path(
"tasks/",
tasks.TaskListView.as_view(),
name="tasks",
),
# Event Notification Transpots
path(
"events/transports/create/",

View File

@ -1,4 +1,6 @@
"""authentik Application administration"""
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
@ -7,8 +9,9 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.forms.applications import ApplicationForm
from authentik.core.models import Application
from authentik.lib.views import CreateAssignPermView
@ -16,7 +19,6 @@ from authentik.lib.views import CreateAssignPermView
class ApplicationCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -31,10 +33,25 @@ class ApplicationCreateView(
template_name = "generic/create.html"
success_message = _("Successfully created Application")
def get_initial(self) -> dict[str, Any]:
if "provider" in self.request.GET:
try:
initial_provider_pk = int(self.request.GET["provider"])
except ValueError:
return super().get_initial()
providers = (
get_objects_for_user(self.request.user, "authentik_core.view_provider")
.filter(pk=initial_provider_pk)
.select_subclasses()
)
if not providers.exists():
return {}
return {"provider": providers.first()}
return super().get_initial()
class ApplicationUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,

View File

@ -7,16 +7,11 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from django.views.generic import UpdateView
from django.views.generic.edit import FormView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.forms import (
CertificateKeyPairForm,
@ -26,26 +21,8 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.lib.views import CreateAssignPermView
class CertificateKeyPairListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all keypairs"""
model = CertificateKeyPair
permission_required = "authentik_crypto.view_certificatekeypair"
ordering = "name"
template_name = "administration/certificatekeypair/list.html"
search_fields = ["name"]
class CertificateKeyPairCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -57,13 +34,12 @@ class CertificateKeyPairCreateView(
permission_required = "authentik_crypto.add_certificatekeypair"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:certificate_key_pair")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Certificate-Key Pair")
class CertificateKeyPairGenerateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
FormView,
@ -75,7 +51,7 @@ class CertificateKeyPairGenerateView(
permission_required = "authentik_crypto.add_certificatekeypair"
template_name = "administration/certificatekeypair/generate.html"
success_url = reverse_lazy("authentik_admin:certificate_key_pair")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully generated Certificate-Key Pair")
def form_valid(self, form: CertificateKeyPairGenerateForm) -> HttpResponse:
@ -91,7 +67,6 @@ class CertificateKeyPairGenerateView(
class CertificateKeyPairUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -103,7 +78,7 @@ class CertificateKeyPairUpdateView(
permission_required = "authentik_crypto.change_certificatekeypair"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:certificate_key_pair")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Certificate-Key Pair")
@ -116,5 +91,5 @@ class CertificateKeyPairDeleteView(
permission_required = "authentik_crypto.delete_certificatekeypair"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:certificate_key_pair")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Certificate-Key Pair")

View File

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
from authentik.admin.views.utils import DeleteMessageView
from authentik.events.forms import NotificationRuleForm
from authentik.events.models import NotificationRule
from authentik.lib.views import CreateAssignPermView
@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView
class NotificationRuleCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -34,7 +33,6 @@ class NotificationRuleCreateView(
class NotificationRuleUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,

View File

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
from authentik.admin.views.utils import DeleteMessageView
from authentik.events.forms import NotificationTransportForm
from authentik.events.models import NotificationTransport
from authentik.lib.views import CreateAssignPermView
@ -16,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView
class NotificationTransportCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -33,7 +32,6 @@ class NotificationTransportCreateView(
class NotificationTransportUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,

View File

@ -8,15 +8,10 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import DetailView, FormView, ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import DetailView, FormView, UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.forms import FlowForm, FlowImportForm
from authentik.flows.models import Flow
@ -29,25 +24,8 @@ from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import CreateAssignPermView, bad_request_message
class FlowListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all flows"""
model = Flow
permission_required = "authentik_flows.view_flow"
ordering = "name"
template_name = "administration/flow/list.html"
search_fields = ["name", "slug", "designation", "title"]
class FlowCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -59,13 +37,12 @@ class FlowCreateView(
permission_required = "authentik_flows.add_flow"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:flows")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Flow")
class FlowUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -77,7 +54,7 @@ class FlowUpdateView(
permission_required = "authentik_flows.change_flow"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:flows")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Flow")
@ -88,7 +65,7 @@ class FlowDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV
permission_required = "authentik_flows.delete_flow"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:flows")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Flow")
@ -128,7 +105,7 @@ class FlowImportView(LoginRequiredMixin, FormView):
form_class = FlowImportForm
template_name = "administration/flow/import.html"
success_url = reverse_lazy("authentik_admin:flows")
success_url = reverse_lazy("authentik_core:shell")
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser:

View File

@ -6,39 +6,17 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.forms.groups import GroupForm
from authentik.core.models import Group
from authentik.lib.views import CreateAssignPermView
class GroupListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all groups"""
model = Group
permission_required = "authentik_core.view_group"
ordering = "name"
template_name = "administration/group/list.html"
search_fields = ["name", "attributes"]
class GroupCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -50,13 +28,12 @@ class GroupCreateView(
permission_required = "authentik_core.add_group"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:groups")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Group")
class GroupUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -68,7 +45,7 @@ class GroupUpdateView(
permission_required = "authentik_core.change_group"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:groups")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Group")
@ -79,5 +56,5 @@ class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_flows.delete_group"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:groups")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Group")

View File

@ -1,6 +1,6 @@
"""authentik Outpost administration"""
from dataclasses import asdict
from typing import Any, Dict
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
@ -11,7 +11,7 @@ from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.outposts.forms import OutpostForm
from authentik.outposts.models import Outpost, OutpostConfig
@ -19,7 +19,6 @@ from authentik.outposts.models import Outpost, OutpostConfig
class OutpostCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -33,7 +32,7 @@ class OutpostCreateView(
template_name = "generic/create.html"
success_message = _("Successfully created Outpost")
def get_initial(self) -> Dict[str, Any]:
def get_initial(self) -> dict[str, Any]:
return {
"_config": asdict(
OutpostConfig(authentik_host=self.request.build_absolute_uri("/"))
@ -43,7 +42,6 @@ class OutpostCreateView(
class OutpostUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,

View File

@ -6,39 +6,18 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.outposts.models import OutpostServiceConnection
class OutpostServiceConnectionListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all outpost-service-connections"""
model = OutpostServiceConnection
permission_required = "authentik_outposts.add_outpostserviceconnection"
template_name = "administration/outpost_service_connection/list.html"
ordering = "pk"
search_fields = ["pk", "name"]
class OutpostServiceConnectionCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -49,13 +28,12 @@ class OutpostServiceConnectionCreateView(
permission_required = "authentik_outposts.add_outpostserviceconnection"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections")
success_message = _("Successfully created OutpostServiceConnection")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Outpost Service Connection")
class OutpostServiceConnectionUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
@ -66,8 +44,8 @@ class OutpostServiceConnectionUpdateView(
permission_required = "authentik_outposts.change_outpostserviceconnection"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections")
success_message = _("Successfully updated OutpostServiceConnection")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Outpost Service Connection")
class OutpostServiceConnectionDeleteView(
@ -79,5 +57,5 @@ class OutpostServiceConnectionDeleteView(
permission_required = "authentik_outposts.delete_outpostserviceconnection"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:outpost-service-connections")
success_message = _("Successfully deleted OutpostServiceConnection")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Outpost Service Connection")

View File

@ -1,5 +1,5 @@
"""authentik Policy administration"""
from typing import Any, Dict
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
@ -11,41 +11,20 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import FormView
from django.views.generic.detail import DetailView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess, PolicyRequest
class PolicyListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all policies"""
model = Policy
permission_required = "authentik_policies.view_policy"
ordering = "name"
template_name = "administration/policy/list.html"
search_fields = ["name"]
class PolicyCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -56,13 +35,12 @@ class PolicyCreateView(
permission_required = "authentik_policies.add_policy"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Policy")
class PolicyUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
@ -73,7 +51,7 @@ class PolicyUpdateView(
permission_required = "authentik_policies.change_policy"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Policy")
@ -84,7 +62,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_policies.delete_policy"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Policy")
@ -102,7 +80,7 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
)
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs["policy"] = self.get_object()
return super().get_context_data(**kwargs)
@ -116,7 +94,7 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
p_request = PolicyRequest(user)
p_request.debug = True
p_request.http_request = self.request
p_request.set_http_request(self.request)
p_request.context = form.cleaned_data.get("context", {})
proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None)

View File

@ -6,55 +6,20 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max, QuerySet
from django.db.models import Max
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.policies.forms import PolicyBindingForm
from authentik.policies.models import PolicyBinding, PolicyBindingModel
class PolicyBindingListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView
):
"""Show list of all policies"""
model = PolicyBinding
permission_required = "authentik_policies.view_policybinding"
ordering = ["order", "target"]
template_name = "administration/policy_binding/list.html"
def get_queryset(self) -> QuerySet:
# Since `select_subclasses` does not work with a foreign key, we have to do two queries here
# First, get all pbm objects that have bindings attached
objects = (
get_objects_for_user(
self.request.user, "authentik_policies.view_policybindingmodel"
)
.filter(policies__isnull=False)
.select_subclasses()
.select_related()
.order_by("pk")
)
for pbm in objects:
pbm.bindings = get_objects_for_user(
self.request.user, self.permission_required
).filter(target__pk=pbm.pbm_uuid)
return objects
class PolicyBindingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -66,7 +31,7 @@ class PolicyBindingCreateView(
form_class = PolicyBindingForm
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created PolicyBinding")
def get_initial(self) -> dict[str, Any]:
@ -88,7 +53,6 @@ class PolicyBindingCreateView(
class PolicyBindingUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -100,7 +64,7 @@ class PolicyBindingUpdateView(
form_class = PolicyBindingForm
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated PolicyBinding")
@ -113,5 +77,5 @@ class PolicyBindingDeleteView(
permission_required = "authentik_policies.delete_policybinding"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted PolicyBinding")

View File

@ -15,7 +15,6 @@ from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceUpdateView,
@ -25,7 +24,6 @@ from authentik.core.models import PropertyMapping
class PropertyMappingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -41,7 +39,6 @@ class PropertyMappingCreateView(
class PropertyMappingUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,

View File

@ -8,7 +8,6 @@ from django.utils.translation import gettext as _
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceUpdateView,
@ -18,7 +17,6 @@ from authentik.core.models import Provider
class ProviderCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -34,7 +32,6 @@ class ProviderCreateView(
class ProviderUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,

View File

@ -8,7 +8,6 @@ from django.utils.translation import gettext as _
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceUpdateView,
@ -18,7 +17,6 @@ from authentik.core.models import Source
class SourceCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -35,7 +33,6 @@ class SourceCreateView(
class SourceUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,

View File

@ -6,39 +6,18 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.flows.models import Stage
class StageListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all stages"""
model = Stage
template_name = "administration/stage/list.html"
permission_required = "authentik_flows.view_stage"
ordering = "name"
search_fields = ["name"]
class StageCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
@ -49,13 +28,12 @@ class StageCreateView(
template_name = "generic/create.html"
permission_required = "authentik_flows.add_stage"
success_url = reverse_lazy("authentik_admin:stages")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Stage")
class StageUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
@ -65,7 +43,7 @@ class StageUpdateView(
model = Stage
permission_required = "authentik_flows.update_application"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stages")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Stage")
@ -75,5 +53,5 @@ class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
model = Stage
template_name = "generic/delete.html"
permission_required = "authentik_flows.delete_stage"
success_url = reverse_lazy("authentik_admin:stages")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Stage")

View File

@ -9,33 +9,17 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Max
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.flows.forms import FlowStageBindingForm
from authentik.flows.models import Flow, FlowStageBinding
from authentik.lib.views import CreateAssignPermView
class StageBindingListView(
LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView
):
"""Show list of all flows"""
model = FlowStageBinding
permission_required = "authentik_flows.view_flowstagebinding"
ordering = ["target", "order"]
template_name = "administration/stage_binding/list.html"
class StageBindingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -47,7 +31,7 @@ class StageBindingCreateView(
form_class = FlowStageBindingForm
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created StageBinding")
def get_initial(self) -> dict[str, Any]:
@ -67,7 +51,6 @@ class StageBindingCreateView(
class StageBindingUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -79,7 +62,7 @@ class StageBindingUpdateView(
form_class = FlowStageBindingForm
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated StageBinding")
@ -92,5 +75,5 @@ class StageBindingDeleteView(
permission_required = "authentik_flows.delete_flowstagebinding"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-bindings")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted FlowStageBinding")

View File

@ -7,39 +7,16 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.stages.invitation.forms import InvitationForm
from authentik.stages.invitation.models import Invitation
class InvitationListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all invitations"""
model = Invitation
permission_required = "authentik_stages_invitation.view_invitation"
template_name = "administration/stage_invitation/list.html"
ordering = "-expires"
search_fields = ["created_by__username", "expires", "fixed_data"]
class InvitationCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -51,7 +28,7 @@ class InvitationCreateView(
permission_required = "authentik_stages_invitation.add_invitation"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Invitation")
def form_valid(self, form):
@ -70,5 +47,5 @@ class InvitationDeleteView(
permission_required = "authentik_stages_invitation.delete_invitation"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Invitation")

View File

@ -6,44 +6,17 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.stages.prompt.forms import PromptAdminForm
from authentik.stages.prompt.models import Prompt
class PromptListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all prompts"""
model = Prompt
permission_required = "authentik_stages_prompt.view_prompt"
ordering = "order"
template_name = "administration/stage_prompt/list.html"
search_fields = [
"field_key",
"label",
"type",
"placeholder",
]
class PromptCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -55,13 +28,12 @@ class PromptCreateView(
permission_required = "authentik_stages_prompt.add_prompt"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Prompt")
class PromptUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -73,7 +45,7 @@ class PromptUpdateView(
permission_required = "authentik_stages_prompt.change_prompt"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Prompt")
@ -84,5 +56,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_stages_prompt.delete_prompt"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Prompt")

View File

@ -1,23 +0,0 @@
"""authentik Tasks List"""
from typing import Any, Dict
from django.views.generic.base import TemplateView
from authentik.admin.mixins import AdminRequiredMixin
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
class TaskListView(AdminRequiredMixin, TemplateView):
"""Show list of all background tasks"""
template_name = "administration/task/list.html"
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
kwargs["object_list"] = sorted(
TaskInfo.all().values(), key=lambda x: x.task_name
)
kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL
kwargs["task_warning"] = TaskResultStatus.WARNING
kwargs["task_error"] = TaskResultStatus.ERROR
return kwargs

View File

@ -2,38 +2,12 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.models import Token
class TokenListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all tokens"""
model = Token
permission_required = "authentik_core.view_token"
ordering = "expires"
template_name = "administration/token/list.html"
search_fields = [
"identifier",
"intent",
"user__username",
"description",
]
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
"""Delete token"""
@ -41,5 +15,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_core.delete_token"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:tokens")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Token")

View File

@ -8,49 +8,20 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, UpdateView
from guardian.mixins import (
PermissionListMixin,
PermissionRequiredMixin,
get_anonymous_user,
)
from django.views.generic import DetailView, UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.users import UserForm
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.models import Token, User
from authentik.lib.views import CreateAssignPermView
class UserListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all users"""
model = User
permission_required = "authentik_core.view_user"
ordering = "username"
template_name = "administration/user/list.html"
search_fields = ["username", "name", "attributes"]
def get_queryset(self):
return super().get_queryset().exclude(pk=get_anonymous_user().pk)
class UserCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
@ -62,13 +33,12 @@ class UserCreateView(
permission_required = "authentik_core.add_user"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:users")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created User")
class UserUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
@ -82,7 +52,7 @@ class UserUpdateView(
# By default the object's name is user which is used by other checks
context_object_name = "object"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:users")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated User")
@ -95,13 +65,11 @@ class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV
# By default the object's name is user which is used by other checks
context_object_name = "object"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:users")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted User")
class UserDisableView(
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView
):
class UserDisableView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
"""Disable user"""
object: User
@ -112,7 +80,7 @@ class UserDisableView(
# By default the object's name is user which is used by other checks
context_object_name = "object"
template_name = "administration/user/disable.html"
success_url = reverse_lazy("authentik_admin:users")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully disabled User")
def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
@ -123,9 +91,7 @@ class UserDisableView(
return HttpResponseRedirect(success_url)
class UserEnableView(
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView
):
class UserEnableView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
"""Enable user"""
object: User
@ -135,15 +101,14 @@ class UserEnableView(
# By default the object's name is user which is used by other checks
context_object_name = "object"
success_url = reverse_lazy("authentik_admin:users")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully enabled User")
def get(self, request: HttpRequest, *args, **kwargs):
self.object: User = self.get_object()
success_url = self.get_success_url()
self.object.is_active = True
self.object.save()
return HttpResponseRedirect(success_url)
return HttpResponseRedirect(self.success_url)
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
@ -160,9 +125,7 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
)
querystring = urlencode({"token": token.key})
link = request.build_absolute_uri(
reverse("authentik_flows:default-recovery") + f"?{querystring}"
reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}"
)
messages.success(
request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})
)
return redirect("authentik_admin:users")
messages.success(request, _("Password reset link: %(link)s" % {"link": link}))
return redirect("/")

View File

@ -1,15 +1,11 @@
"""authentik admin util views"""
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
from typing import Any
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.postgres.search import SearchQuery, SearchVector
from django.db.models import QuerySet
from django.http import Http404
from django.http.request import HttpRequest
from django.views.generic import DeleteView, ListView, UpdateView
from django.views.generic.list import MultipleObjectMixin
from django.urls import reverse_lazy
from django.views.generic import DeleteView, UpdateView
from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.views import CreateAssignPermView
@ -18,42 +14,13 @@ from authentik.lib.views import CreateAssignPermView
class DeleteMessageView(SuccessMessageMixin, DeleteView):
"""DeleteView which shows `self.success_message` on successful deletion"""
success_url = reverse_lazy("authentik_core:shell")
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)
class InheritanceListView(ListView):
"""ListView for objects using InheritanceManager"""
def get_context_data(self, **kwargs):
kwargs["types"] = {x.__name__: x for x in all_subclasses(self.model)}
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().select_subclasses()
class SearchListMixin(MultipleObjectMixin):
"""Accept search query using `search` querystring parameter. Requires self.search_fields,
a list of all fields to search. Can contain special lookups like __icontains"""
search_fields: List[str]
def get_queryset(self) -> QuerySet:
queryset = super().get_queryset()
if "search" in self.request.GET:
raw_query = self.request.GET["search"]
if raw_query == "":
# Empty query, don't search at all
return queryset
search = SearchQuery(raw_query, search_type="websearch")
return queryset.annotate(search=SearchVector(*self.search_fields)).filter(
search=search
)
return queryset
class InheritanceCreateView(CreateAssignPermView):
"""CreateView for objects using InheritanceManager"""
@ -67,7 +34,7 @@ class InheritanceCreateView(CreateAssignPermView):
raise Http404 from exc
return model().form
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
form_cls = self.get_form_class()
if hasattr(form_cls, "template_name"):
@ -78,7 +45,7 @@ class InheritanceCreateView(CreateAssignPermView):
class InheritanceUpdateView(UpdateView):
"""UpdateView for objects using InheritanceManager"""
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
form_cls = self.get_form_class()
if hasattr(form_cls, "template_name"):
@ -94,31 +61,3 @@ class InheritanceUpdateView(UpdateView):
.select_subclasses()
.first()
)
class BackSuccessUrlMixin:
"""Checks if a relative URL has been given as ?back param, and redirect to it. Otherwise
default to self.success_url."""
request: HttpRequest
success_url: Optional[str]
def get_success_url(self) -> str:
"""get_success_url from FormMixin"""
back_param = self.request.GET.get("back")
if back_param:
if not bool(urlparse(back_param).netloc):
return back_param
return str(self.success_url)
class UserPaginateListMixin:
"""Get paginate_by value from user's attributes, defaulting to 15"""
request: HttpRequest
# pylint: disable=unused-argument
def get_paginate_by(self, queryset: QuerySet) -> int:
"""get_paginate_by Function of ListView"""
return self.request.user.attributes.get("paginate_by", 15)

View File

@ -1,7 +1,7 @@
"""API Authentication"""
from base64 import b64decode
from binascii import Error
from typing import Any, Optional, Tuple, Union
from typing import Any, Optional, Union
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.request import Request
@ -44,7 +44,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
class AuthentikTokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Basic authentication"""
def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]:
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
"""Token-based authentication using HTTP Basic authentication"""
auth = get_authorization_header(request)

View File

@ -23,23 +23,17 @@ from authentik.events.api.event import EventViewSet
from authentik.events.api.notification import NotificationViewSet
from authentik.events.api.notification_rule import NotificationRuleViewSet
from authentik.events.api.notification_transport import NotificationTransportViewSet
from authentik.flows.api import (
FlowCacheViewSet,
FlowStageBindingViewSet,
FlowViewSet,
StageViewSet,
)
from authentik.flows.api.bindings import FlowStageBindingViewSet
from authentik.flows.api.flows import FlowViewSet
from authentik.flows.api.stages import StageViewSet
from authentik.flows.views import FlowExecutorView
from authentik.outposts.api.outpost_service_connections import (
DockerServiceConnectionViewSet,
KubernetesServiceConnectionViewSet,
ServiceConnectionViewSet,
)
from authentik.outposts.api.outposts import OutpostViewSet
from authentik.policies.api import (
PolicyBindingViewSet,
PolicyCacheViewSet,
PolicyViewSet,
)
from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet
from authentik.policies.dummy.api import DummyPolicyViewSet
from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet
from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet
@ -47,7 +41,11 @@ from authentik.policies.expression.api import ExpressionPolicyViewSet
from authentik.policies.group_membership.api import GroupMembershipPolicyViewSet
from authentik.policies.hibp.api import HaveIBeenPwendPolicyViewSet
from authentik.policies.password.api import PasswordPolicyViewSet
from authentik.policies.reputation.api import ReputationPolicyViewSet
from authentik.policies.reputation.api import (
IPReputationViewSet,
ReputationPolicyViewSet,
UserReputationViewSet,
)
from authentik.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet
from authentik.providers.proxy.api import (
ProxyOutpostConfigViewSet,
@ -57,15 +55,19 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
from authentik.sources.oauth.api import OAuthSourceViewSet
from authentik.sources.saml.api import SAMLSourceViewSet
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet
from authentik.stages.authenticator_validate.api import (
AuthenticatorValidateStageViewSet,
)
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet
from authentik.stages.captcha.api import CaptchaStageViewSet
from authentik.stages.consent.api import ConsentStageViewSet
from authentik.stages.deny.api import DenyStageViewSet
from authentik.stages.dummy.api import DummyStageViewSet
from authentik.stages.email.api import EmailStageViewSet
from authentik.stages.identification.api import IdentificationStageViewSet
from authentik.stages.invitation.api import InvitationStageViewSet, InvitationViewSet
from authentik.stages.otp_static.api import OTPStaticStageViewSet
from authentik.stages.otp_time.api import OTPTimeStageViewSet
from authentik.stages.otp_validate.api import OTPValidateStageViewSet
from authentik.stages.password.api import PasswordStageViewSet
from authentik.stages.prompt.api import PromptStageViewSet, PromptViewSet
from authentik.stages.user_delete.api import UserDeleteStageViewSet
@ -97,7 +99,6 @@ router.register(
router.register("outposts/proxy", ProxyOutpostConfigViewSet)
router.register("flows/instances", FlowViewSet)
router.register("flows/cached", FlowCacheViewSet, basename="flows_cache")
router.register("flows/bindings", FlowStageBindingViewSet)
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
@ -113,7 +114,6 @@ router.register("sources/saml", SAMLSourceViewSet)
router.register("sources/oauth", OAuthSourceViewSet)
router.register("policies/all", PolicyViewSet)
router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache")
router.register("policies/bindings", PolicyBindingViewSet)
router.register("policies/expression", ExpressionPolicyViewSet)
router.register("policies/event_matcher", EventMatcherPolicyViewSet)
@ -121,6 +121,8 @@ router.register("policies/group_membership", GroupMembershipPolicyViewSet)
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
router.register("policies/password_expiry", PasswordExpiryPolicyViewSet)
router.register("policies/password", PasswordPolicyViewSet)
router.register("policies/reputation/users", UserReputationViewSet)
router.register("policies/reputation/ips", IPReputationViewSet)
router.register("policies/reputation", ReputationPolicyViewSet)
router.register("providers/all", ProviderViewSet)
@ -134,15 +136,17 @@ router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register("propertymappings/scope", ScopeMappingViewSet)
router.register("stages/all", StageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
router.register("stages/authenticator/validate", AuthenticatorValidateStageViewSet)
router.register("stages/authenticator/webauthn", AuthenticateWebAuthnStageViewSet)
router.register("stages/captcha", CaptchaStageViewSet)
router.register("stages/consent", ConsentStageViewSet)
router.register("stages/deny", DenyStageViewSet)
router.register("stages/email", EmailStageViewSet)
router.register("stages/identification", IdentificationStageViewSet)
router.register("stages/invitation", InvitationStageViewSet)
router.register("stages/invitation/invitations", InvitationViewSet)
router.register("stages/otp_static", OTPStaticStageViewSet)
router.register("stages/otp_time", OTPTimeStageViewSet)
router.register("stages/otp_validate", OTPValidateStageViewSet)
router.register("stages/invitation/stages", InvitationStageViewSet)
router.register("stages/password", PasswordStageViewSet)
router.register("stages/prompt/prompts", PromptViewSet)
router.register("stages/prompt/stages", PromptStageViewSet)
@ -180,4 +184,9 @@ urlpatterns = [
name="schema-swagger-ui",
),
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
path(
"flows/executor/<slug:flow_slug>/",
FlowExecutorView.as_view(),
name="flow-executor",
),
] + router.urls

View File

@ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"]

View File

@ -1,9 +1,16 @@
"""PropertyMapping API Views"""
from django.urls import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import PropertyMapping
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
@ -47,3 +54,19 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet):
def get_queryset(self):
return PropertyMapping.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable property-mapping types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:property-mapping-create")
+ f"?type={subclass.__name__}",
}
)
return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -1,5 +1,5 @@
"""Provider API Views"""
from django.shortcuts import reverse
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action

View File

@ -1,5 +1,5 @@
"""Source API Views"""
from django.shortcuts import reverse
from django.urls import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
@ -25,7 +25,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
class Meta:
model = Source
fields = SOURCE_SERIALIZER_FIELDS = [
fields = [
"pk",
"name",
"slug",

View File

@ -9,6 +9,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.users import UserSerializer
from authentik.core.models import Token
from authentik.events.models import Event, EventAction
@ -16,10 +17,21 @@ from authentik.events.models import Event, EventAction
class TokenSerializer(ModelSerializer):
"""Token Serializer"""
user = UserSerializer()
class Meta:
model = Token
fields = ["pk", "identifier", "intent", "user", "description"]
fields = [
"pk",
"identifier",
"intent",
"user",
"description",
"expires",
"expiring",
]
depth = 2
class TokenViewSerializer(Serializer):
@ -40,6 +52,19 @@ class TokenViewSet(ModelViewSet):
lookup_field = "identifier"
queryset = Token.filter_not_expired()
serializer_class = TokenSerializer
search_fields = [
"identifier",
"intent",
"user__username",
"description",
]
filterset_fields = [
"identifier",
"intent",
"user__username",
"description",
]
ordering = ["expires"]
@swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
@action(detail=True)

View File

@ -2,33 +2,35 @@
from drf_yasg2.utils import swagger_auto_schema
from guardian.utils import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
BooleanField,
ModelSerializer,
SerializerMethodField,
)
from rest_framework.serializers import BooleanField, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.models import User
from authentik.lib.templatetags.authentik_utils import avatar
class UserSerializer(ModelSerializer):
"""User Serializer"""
is_superuser = BooleanField(read_only=True)
avatar = SerializerMethodField()
def get_avatar(self, user: User) -> str:
"""Add user's avatar as URL"""
return avatar(user)
avatar = CharField(read_only=True)
class Meta:
model = User
fields = ["pk", "username", "name", "is_superuser", "email", "avatar"]
fields = [
"pk",
"username",
"name",
"is_active",
"last_login",
"is_superuser",
"email",
"avatar",
"attributes",
]
class UserViewSet(ModelViewSet):
@ -36,6 +38,8 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.none()
serializer_class = UserSerializer
search_fields = ["username", "name", "is_active"]
filterset_fields = ["username", "name", "is_active"]
def get_queryset(self):
return User.objects.all().exclude(pk=get_anonymous_user().pk)

View File

@ -1,6 +1,6 @@
"""API Utilities"""
from django.db.models import Model
from rest_framework.fields import CharField
from rest_framework.fields import CharField, IntegerField
from rest_framework.serializers import Serializer, SerializerMethodField
@ -37,3 +37,15 @@ class TypeCreateSerializer(Serializer):
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class CacheSerializer(Serializer):
"""Generic cache stats for an object"""
count = IntegerField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError

View File

@ -9,10 +9,10 @@ from authentik.lib.widgets import GroupedModelChoiceField
class ApplicationForm(forms.ModelForm):
"""Application Form"""
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs): # pragma: no cover
super().__init__(*args, **kwargs)
self.fields["provider"].queryset = (
Provider.objects.all().order_by("pk").select_subclasses()
Provider.objects.all().order_by("name").select_subclasses()
)
class Meta:

View File

@ -1,15 +1,20 @@
"""authentik core models"""
from datetime import timedelta
from typing import Any, Dict, Optional, Type
from hashlib import md5, sha256
from typing import Any, Optional, Type
from urllib.parse import urlencode
from uuid import uuid4
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
from django.db.models import Q, QuerySet
from django.forms import ModelForm
from django.http import HttpRequest
from django.templatetags.static import static
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from guardian.mixins import GuardianUserMixin
@ -21,6 +26,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.signals import password_changed
from authentik.core.types import UILoginButton
from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.managed.models import ManagedModel
from authentik.policies.models import PolicyBindingModel
@ -29,6 +35,9 @@ LOGGER = get_logger()
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("dist/assets/images/user_default.png")
def default_token_duration():
"""Default duration a Token is valid"""
@ -94,7 +103,7 @@ class User(GuardianUserMixin, AbstractUser):
objects = UserManager()
def group_attributes(self) -> Dict[str, Any]:
def group_attributes(self) -> dict[str, Any]:
"""Get a dictionary containing the attributes from all groups the user belongs to,
including the users attributes"""
final_attributes = {}
@ -119,6 +128,30 @@ class User(GuardianUserMixin, AbstractUser):
self.password_change_date = now()
return super().set_password(password)
@property
def uid(self) -> str:
"""Generate a globall unique UID, based on the user ID and the hashed secret key"""
return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
@property
def avatar(self) -> str:
"""Get avatar, depending on authentik.avatar setting"""
mode = CONFIG.raw.get("authentik").get("avatars")
if mode == "none":
return DEFAULT_AVATAR
if mode == "gravatar":
parameters = [
("s", "158"),
("r", "g"),
]
# gravatar uses md5 for their URLs, so md5 can't be avoided
mail_hash = md5(self.email.encode("utf-8")).hexdigest() # nosec
gravatar_url = (
f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}"
)
return escape(gravatar_url)
raise ValueError(f"Invalid avatar mode {mode}")
class Meta:
permissions = (
@ -252,11 +285,6 @@ class Source(SerializerModel, PolicyBindingModel):
button. If source doesn't use http-based flow, return None."""
return None
@property
def ui_additional_info(self) -> Optional[str]:
"""Return additional Info, such as a callback URL. Show in the administration interface."""
return None
@property
def ui_user_settings(self) -> Optional[str]:
"""Entrypoint to integrate with User settings. Can either return None if no

View File

@ -16,7 +16,6 @@
<link rel="stylesheet" type="text/css" href="{% static 'dist/fontawesome.min.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}">
<script src="{% url 'javascript-catalog' %}?v={{ ak_version }}"></script>
<script src="{% static 'dist/main.js' %}?v={{ ak_version }}" type="module"></script>
{% block head %}
{% endblock %}
</head>

View File

@ -1,31 +0,0 @@
{% extends "login/base.html" %}
{% load authentik_utils %}
{% load i18n %}
{% block title %}
{{ title }}
{% endblock %}
{% block card %}
<form method="POST" action="{{ url }}" autosubmit>
{% csrf_token %}
{% for key, value in attrs.items %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
<div class="pf-c-form__group pf-u-display-flex pf-u-justify-content-center">
<div class="pf-c-form__group-control">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__actions">
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans 'Continue' %}</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -1,53 +0,0 @@
{% load i18n %}
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-applications"></i>
{% trans 'Applications' %}
</h1>
</div>
</section>
<section class="pf-c-page__main-section">
{% if applications %}
<div class="pf-l-gallery pf-m-gutter">
{% for app in applications %}
<a href="{{ app.get_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact ak-root-link">
<div class="pf-c-card__header">
{% if app.meta_icon %}
<img class="app-icon pf-c-avatar" src="{{ app.meta_icon.url }}" alt="{% trans 'Application Icon' %}">
{% else %}
<i class="pf-icon pf-icon-arrow"></i>
{% endif %}
</div>
<div class="pf-c-card__title">
<p id="card-1-check-label">{{ app.name }}</p>
<div class="pf-c-content">
<small>{{ app.meta_publisher }}</small>
</div>
</div>
<div class="pf-c-card__body">
{% trans app.meta_description|truncatewords:35 %}
</div>
</a>
{% endfor %}
</div>
{% else %}
<div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">{% trans 'No Applications available.' %}</h1>
<div class="pf-c-empty-state__body">
{% trans "Either no applications are defined, or you don't have access to any." %}
</div>
{% if perms.authentik_core.add_application %}
<a href="{% url 'authentik_admin:application-create' %}" class="pf-c-button pf-m-primary" type="button">
{% trans 'Create Application' %}
</a>
{% endif %}
</div>
</div>
{% endif %}
</section>
</main>

View File

@ -28,10 +28,8 @@
{% for source in sources %}
<li class="pf-c-login__main-footer-links-item">
<a href="{{ source.url }}" class="pf-c-login__main-footer-links-item-link">
{% if source.icon_path %}
<img src="{% static source.icon_path %}" alt="{{ source.name }}">
{% elif source.icon_url %}
<img src="icon_url" alt="{{ source.name }}">
{% if source.icon_url %}
<img src="{{ source.icon_url }}" alt="{{ source.name }}">
{% else %}
<i class="pf-icon pf-icon-arrow" title="{{ source.name }}"></i>
{% endif %}

View File

@ -1,19 +0,0 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% block card %}
<form method="POST" class="pf-c-form">
{% block above_form %}
{% endblock %}
{% include 'partials/form.html' %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans primary_action %}</button>
</div>
</form>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends 'login/form.html' %}
{% load i18n %}
{% load authentik_utils %}
{% block above_form %}
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="left">
<img class="pf-c-avatar" src="{% avatar user %}" alt="">
{{ user.username }}
</div>
<div class="right">
<a href="{% url 'authentik_flows:cancel' %}">{% trans 'Not you?' %}</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,24 +0,0 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% load authentik_utils %}
{% block title %}
{% trans title %}
{% endblock %}
{% block head %}
<meta http-equiv="refresh" content="0; url={{ target_url }}" />
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans title %}</h1>
</header>
<form>
<div class="form-group">
<div class="spinner spinner-lg"></div>
</div>
</form>
{% endblock %}

View File

@ -3,7 +3,7 @@
{% csrf_token %}
{% if form.non_field_errors %}
<div class="pf-c-form__group has-error">
<div class="pf-c-form__group">
<p class="pf-c-form__helper-text pf-m-error">
{{ form.non_field_errors }}
</p>
@ -13,7 +13,7 @@
{% if field.field.widget|fieldtype == 'HiddenInput' %}
{{ field }}
{% else %}
<div class="pf-c-form__group {% if field.errors %} has-error {% endif %}">
<div class="pf-c-form__group">
{% if field.field.widget|fieldtype == 'RadioSelect' %}
<label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %}
for="{{ field.name }}-{{ forloop.counter0 }}">

View File

@ -1,42 +0,0 @@
{% load i18n %}
{% load authentik_utils %}
<div class="pf-c-toolbar__item pf-m-pagination ">
<div class="pf-c-pagination pf-m-compact pf-m-hidden pf-m-visible-on-md">
<div class="pf-c-pagination pf-m-compact pf-m-compact pf-m-hidden pf-m-visible-on-md">
<div class="pf-c-options-menu">
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
<span class="pf-c-options-menu__toggle-text">
{% blocktrans with start_index=page_obj.start_index end_index=page_obj.end_index total_items=paginator.count %}
{{ start_index }} - {{ end_index }} of {{ total_items }}
{% endblocktrans %}
</span>
</div>
</div>
<nav class="pf-c-pagination__nav" aria-label="Pagination">
<div class="pf-c-pagination__nav-control pf-m-prev">
<a class="pf-c-button pf-m-plain"
{% if page_obj.has_previous %}
href="{{ request.path }}?{% query_transform page=page_obj.previous_page_number %}"
{% else %}
disabled
{% endif %}
aria-label="{% trans 'Go to previous page' %}">
<i class="fas fa-angle-left" aria-hidden="true"></i>
</a>
</div>
<div class="pf-c-pagination__nav-control pf-m-next">
<a class="pf-c-button pf-m-plain"
{% if page_obj.has_next %}
href="{{ request.path }}?{% query_transform page=page_obj.next_page_number %}"
{% else %}
disabled
{% endif %}
aria-label="{% trans 'Go to next page' %}">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</a>
</div>
</nav>
</div>
</div>
</div>

View File

@ -1,13 +0,0 @@
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<form class="pf-c-input-group" method="GET">
{# include page data for pagination #}
<input type="hidden" name="page" value="{{ page_obj.number }}">
<input class="pf-c-form-control" name="search" type="search" placeholder="Search..." value="{{ request.GET.search }}">
<button class="pf-c-button pf-m-control" type="submit">
<i class="fas fa-search" aria-hidden="true"></i>
</button>
</form>
</div>
</div>

View File

@ -1,5 +1,11 @@
{% extends "base/skeleton.html" %}
{% load static %}
{% block head %}
<script src="{% static 'dist/main.js' %}?v={{ ak_version }}" type="module"></script>
{% endblock %}
{% block body %}
<ak-interface-admin></ak-interface-admin>
{% endblock %}

View File

@ -24,9 +24,7 @@
</div>
</section>
<section slot="page-2" data-tab-title="{% trans 'Tokens' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-site-shell url="{% url 'authentik_core:user-tokens' %}">
<div slot="body"></div>
</ak-site-shell>
<ak-token-user-list></ak-token-user-list>
</section>
{% user_stages as user_stages_loc %}
{% for stage, stage_link in user_stages_loc.items %}

View File

@ -1,100 +0,0 @@
{% load i18n %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
<p>{% trans "Tokens can be used to access authentik's API." %}</p>
</div>
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_core:user-tokens-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
<th role="columnheader" scope="col">{% trans 'Description' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for token in object_list %}
<tr role="row">
<th role="columnheader">
<div>{{ token.identifier }}</div>
</th>
<td role="cell">
<span>
{{ token.expiring|yesno:"Yes,No" }}
</span>
</td>
<td role="cell">
<span>
{% if not token.expiring %}
-
{% else %}
{{ token.expires }}
{% endif %}
</span>
</td>
<td role="cell">
<span>
{{ token.description }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_core:user-tokens-update' identifier=token.identifier %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_core:user-tokens-delete' identifier=token.identifier %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-token-copy-button identifier="{{ token.identifier }}">
{% trans 'Copy token' %}
</ak-token-copy-button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
{% else %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Tokens.' %}
</h1>
<div class="pf-c-empty-state__body">
{% trans 'Currently no tokens exist. Click the button below to create one.' %}
</div>
<ak-modal-button href="{% url 'authentik_core:user-tokens-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>

View File

@ -1,6 +1,6 @@
"""impersonation tests"""
from django.shortcuts import reverse
from django.test.testcases import TestCase
from django.urls import reverse
from authentik.core.models import User

View File

@ -2,8 +2,8 @@
import string
from random import SystemRandom
from django.shortcuts import reverse
from django.test import TestCase
from django.urls import reverse
from authentik.core.models import User
@ -28,9 +28,3 @@ class TestOverviewViews(TestCase):
self.assertEqual(
self.client.get(reverse("authentik_core:shell")).status_code, 200
)
def test_overview(self):
"""Test overview"""
self.assertEqual(
self.client.get(reverse("authentik_core:overview")).status_code, 200
)

View File

@ -2,8 +2,8 @@
import string
from random import SystemRandom
from django.shortcuts import reverse
from django.test import TestCase
from django.urls import reverse
from authentik.core.models import User

View File

@ -2,6 +2,10 @@
from dataclasses import dataclass
from typing import Optional
from django.db.models.base import Model
from rest_framework.fields import CharField
from rest_framework.serializers import Serializer
@dataclass
class UILoginButton:
@ -13,8 +17,19 @@ class UILoginButton:
# URL Which Button points to
url: str
# Icon name, ran through django's static
icon_path: Optional[str] = None
# Icon URL, used as-is
icon_url: Optional[str] = None
class UILoginButtonSerializer(Serializer):
"""Serializer for Login buttons of sources"""
name = CharField()
url = CharField()
icon_url = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()

View File

@ -1,14 +1,13 @@
"""authentik URL Configuration"""
from django.urls import path
from authentik.core.views import impersonate, library, shell, user
from authentik.core.views import impersonate, shell, user
urlpatterns = [
path("", shell.ShellView.as_view(), name="shell"),
# User views
path("-/user/", user.UserSettingsView.as_view(), name="user-settings"),
path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"),
path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"),
path(
"-/user/tokens/create/",
user.TokenCreateView.as_view(),
@ -24,8 +23,6 @@ urlpatterns = [
user.TokenDeleteView.as_view(),
name="user-tokens-delete",
),
# Libray
path("library", library.LibraryView.as_view(), name="overview"),
# Impersonation
path(
"-/impersonation/<int:user_id>/",

View File

@ -1,23 +0,0 @@
"""authentik library view"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from authentik.core.models import Application
from authentik.policies.engine import PolicyEngine
class LibraryView(LoginRequiredMixin, TemplateView):
"""Overview for logged in user, incase user opens authentik directly
and is not being forwarded"""
template_name = "library.html"
def get_context_data(self, **kwargs):
kwargs["applications"] = []
for application in Application.objects.all().order_by("name"):
engine = PolicyEngine(application, self.request.user, self.request)
engine.build()
if engine.passing:
kwargs["applications"].append(application)
return super().get_context_data(**kwargs)

View File

@ -1,25 +1,20 @@
"""authentik core user views"""
from typing import Any, Dict
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models.query import QuerySet
from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from django.views.generic import UpdateView
from django.views.generic.base import TemplateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import get_objects_for_user
from authentik.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.core.forms.token import UserTokenForm
from authentik.core.forms.users import UserDetailForm
from authentik.core.models import Token, TokenIntents
@ -45,7 +40,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
unenrollment_flow = Flow.with_policy(
self.request, designation=FlowDesignation.UNRENOLLMENT
@ -54,30 +49,6 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
return kwargs
class TokenListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all tokens"""
model = Token
ordering = "expires"
permission_required = "authentik_core.view_token"
template_name = "user/token_list.html"
search_fields = [
"identifier",
"intent",
"description",
]
def get_queryset(self) -> QuerySet:
return super().get_queryset().filter(intent=TokenIntents.INTENT_API)
class TokenCreateView(
SuccessMessageMixin,
LoginRequiredMixin,

View File

@ -20,11 +20,16 @@ class CertificateKeyPairSerializer(ModelSerializer):
cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True)
cert_subject = SerializerMethodField()
private_key_available = SerializerMethodField()
def get_cert_subject(self, instance: CertificateKeyPair) -> str:
"""Get certificate subject as full rfc4514"""
return instance.certificate.subject.rfc4514_string()
def get_private_key_available(self, instance: CertificateKeyPair) -> bool:
"""Show if this keypair has a private key configured or not"""
return instance.key_data != "" and instance.key_data is not None
def validate_certificate_data(self, value):
"""Verify that input is a valid PEM x509 Certificate"""
try:
@ -58,6 +63,7 @@ class CertificateKeyPairSerializer(ModelSerializer):
"key_data",
"cert_expiry",
"cert_subject",
"private_key_available",
]
extra_kwargs = {
"key_data": {"write_only": True},

18
authentik/events/geo.py Normal file
View File

@ -0,0 +1,18 @@
"""events GeoIP Reader"""
from typing import Optional
from geoip2.database import Reader
from authentik.lib.config import CONFIG
def get_geoip_reader() -> Optional[Reader]:
"""Get GeoIP Reader, if configured, otherwise none"""
path = CONFIG.y("authentik.geoip")
if path == "" or not path:
return None
return Reader(path)
GEOIP_READER = get_geoip_reader()

View File

@ -5,10 +5,10 @@ from typing import Optional, Union
from uuid import uuid4
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext as _
from geoip2.errors import GeoIP2Error
from requests import RequestException, post
from structlog.stdlib import get_logger
@ -18,6 +18,7 @@ from authentik.core.middleware import (
SESSION_IMPERSONATE_USER,
)
from authentik.core.models import Group, User
from authentik.events.geo import GEOIP_READER
from authentik.events.utils import cleanse_dict, get_user, sanitize_dict
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip
@ -133,22 +134,40 @@ class Event(models.Model):
)
# User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = get_client_ip(request) or "255.255.255.255"
# Apply GeoIP Data, when enabled
self.with_geoip()
# If there's no app set, we get it from the requests too
if not self.app:
self.app = Event._get_app_from_request(request)
self.save()
return self
def with_geoip(self):
"""Apply GeoIP Data, when enabled"""
if not GEOIP_READER:
return
try:
response = GEOIP_READER.city(self.client_ip)
self.context["geo"] = {
"continent": response.continent.code,
"country": response.country.iso_code,
"lat": response.location.latitude,
"long": response.location.longitude,
}
if response.city.name:
self.context["geo"]["city"] = response.city.name
except GeoIP2Error:
pass
def save(self, *args, **kwargs):
if not self._state.adding:
raise ValidationError("you may not edit an existing Event")
LOGGER.debug(
"Created Event",
action=self.action,
context=self.context,
client_ip=self.client_ip,
user=self.user,
)
if self._state.adding:
LOGGER.debug(
"Created Event",
action=self.action,
context=self.context,
client_ip=self.client_ip,
user=self.user,
)
return super().save(*args, **kwargs)
@property

View File

@ -3,7 +3,7 @@ from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from traceback import format_tb
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from celery import Task
from django.core.cache import cache
@ -26,7 +26,7 @@ class TaskResult:
status: TaskResultStatus
messages: List[str] = field(default_factory=list)
messages: list[str] = field(default_factory=list)
# Optional UID used in cache for tasks that run in different instances
uid: Optional[str] = field(default=None)
@ -49,8 +49,8 @@ class TaskInfo:
task_call_module: str
task_call_func: str
task_call_args: List[Any] = field(default_factory=list)
task_call_kwargs: Dict[str, Any] = field(default_factory=dict)
task_call_args: list[Any] = field(default_factory=list)
task_call_kwargs: dict[str, Any] = field(default_factory=dict)
task_description: Optional[str] = field(default=None)
@ -60,7 +60,7 @@ class TaskInfo:
return self.task_name.split("_")
@staticmethod
def all() -> Dict[str, "TaskInfo"]:
def all() -> dict[str, "TaskInfo"]:
"""Get all TaskInfo objects"""
return cache.get_many(cache.keys("task_*"))
@ -109,7 +109,7 @@ class MonitoredTask(Task):
# pylint: disable=too-many-arguments
def after_return(
self, status, retval, task_id, args: List[Any], kwargs: Dict[str, Any], einfo
self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo
):
if not self._result.uid:
self._result.uid = self._uid

View File

@ -1,6 +1,6 @@
"""authentik events signal listener"""
from threading import Thread
from typing import Any, Dict, Optional
from typing import Any, Optional
from django.contrib.auth.signals import (
user_logged_in,
@ -27,7 +27,7 @@ class EventNewThread(Thread):
action: str
request: HttpRequest
kwargs: Dict[str, Any]
kwargs: dict[str, Any]
user: Optional[User] = None
def __init__(
@ -69,7 +69,7 @@ def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
@receiver(user_write)
# pylint: disable=unused-argument
def on_user_write(
sender, request: HttpRequest, user: User, data: Dict[str, Any], **kwargs
sender, request: HttpRequest, user: User, data: dict[str, Any], **kwargs
):
"""Log User write"""
thread = EventNewThread(EventAction.USER_WRITE, request, **data)
@ -81,7 +81,7 @@ def on_user_write(
@receiver(user_login_failed)
# pylint: disable=unused-argument
def on_user_login_failed(
sender, credentials: Dict[str, str], request: HttpRequest, **_
sender, credentials: dict[str, str], request: HttpRequest, **_
):
"""Failed Login"""
thread = EventNewThread(EventAction.LOGIN_FAILED, request, **credentials)

View File

@ -1,6 +1,6 @@
"""Event API tests"""
from django.shortcuts import reverse
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User

View File

@ -1,6 +1,6 @@
"""Event Middleware tests"""
from django.shortcuts import reverse
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import Application, User

View File

@ -1,7 +1,7 @@
"""event utilities"""
import re
from dataclasses import asdict, is_dataclass
from typing import Any, Dict, Optional
from typing import Any, Optional
from uuid import UUID
from django.contrib.auth.models import AnonymousUser
@ -20,7 +20,7 @@ from authentik.policies.types import PolicyRequest
ALLOWED_SPECIAL_KEYS = re.compile("passing", flags=re.I)
def cleanse_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
def cleanse_dict(source: dict[Any, Any]) -> dict[Any, Any]:
"""Cleanse a dictionary, recursively"""
final_dict = {}
for key, value in source.items():
@ -38,7 +38,7 @@ def cleanse_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
return final_dict
def model_to_dict(model: Model) -> Dict[str, Any]:
def model_to_dict(model: Model) -> dict[str, Any]:
"""Convert model to dict"""
name = str(model)
if hasattr(model, "name"):
@ -51,7 +51,7 @@ def model_to_dict(model: Model) -> Dict[str, Any]:
}
def get_user(user: User, original_user: Optional[User] = None) -> Dict[str, Any]:
def get_user(user: User, original_user: Optional[User] = None) -> dict[str, Any]:
"""Convert user object to dictionary, optionally including the original user"""
if isinstance(user, AnonymousUser):
user = get_anonymous_user()
@ -67,7 +67,7 @@ def get_user(user: User, original_user: Optional[User] = None) -> Dict[str, Any]
return user_data
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
"""clean source of all Models that would interfere with the JSONField.
Models are replaced with a dictionary of {
app: str,
@ -85,7 +85,7 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
value = asdict(value)
if isinstance(value, dict):
final_dict[key] = sanitize_dict(value)
elif isinstance(value, User):
elif isinstance(value, (User, AnonymousUser)):
final_dict[key] = sanitize_dict(get_user(value))
elif isinstance(value, models.Model):
final_dict[key] = sanitize_dict(model_to_dict(value))

View File

@ -0,0 +1,35 @@
"""Flow Binding API Views"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.flows.api.stages import StageSerializer
from authentik.flows.models import FlowStageBinding
class FlowStageBindingSerializer(ModelSerializer):
"""FlowStageBinding Serializer"""
stage_obj = StageSerializer(read_only=True, source="stage")
class Meta:
model = FlowStageBinding
fields = [
"pk",
"policybindingmodel_ptr_id",
"target",
"stage",
"stage_obj",
"evaluate_on_plan",
"re_evaluate_policies",
"order",
"policies",
]
class FlowStageBindingViewSet(ModelViewSet):
"""FlowStageBinding Viewset"""
queryset = FlowStageBinding.objects.all()
serializer_class = FlowStageBindingSerializer
filterset_fields = "__all__"

View File

@ -7,7 +7,6 @@ from django.shortcuts import get_object_or_404
from drf_yasg2.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -16,9 +15,10 @@ from rest_framework.serializers import (
Serializer,
SerializerMethodField,
)
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from rest_framework.viewsets import ModelViewSet
from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.core.api.utils import CacheSerializer
from authentik.flows.models import Flow
from authentik.flows.planner import cache_key
@ -81,6 +81,12 @@ class FlowViewSet(ModelViewSet):
search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"]
@swagger_auto_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False)
def cached(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"count": len(cache.keys("flow_*"))})
@swagger_auto_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, methods=["get"])
def diagram(self, request: Request, slug: str) -> Response:
@ -101,13 +107,6 @@ class FlowViewSet(ModelViewSet):
.filter(target=flow)
.order_by("order")
):
body.append(
DiagramElement(
f"stage_{s_index}",
"operation",
f"Stage\n{stage_binding.stage.name}",
)
)
for p_index, policy_binding in enumerate(
get_objects_for_user(
request.user, "authentik_policies.view_policybinding"
@ -122,6 +121,13 @@ class FlowViewSet(ModelViewSet):
f"Policy\n{policy_binding.policy.name}",
)
)
body.append(
DiagramElement(
f"stage_{s_index}",
"operation",
f"Stage\n{stage_binding.stage.name}",
)
)
# If the 2nd last element is a policy, we need to have an item to point to
# for a negative case
body.append(
@ -152,73 +158,3 @@ class FlowViewSet(ModelViewSet):
)
diagram = "\n".join([str(x) for x in header + body + footer])
return Response({"diagram": diagram})
class StageSerializer(ModelSerializer):
"""Stage Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
verbose_name = SerializerMethodField(method_name="get_verbose_name")
def get_type(self, obj: Stage) -> str:
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("stage", "")
def get_verbose_name(self, obj: Stage) -> str:
"""Get verbose name for UI"""
return obj._meta.verbose_name
class Meta:
model = Stage
fields = ["pk", "name", "__type__", "verbose_name"]
class StageViewSet(ReadOnlyModelViewSet):
"""Stage Viewset"""
queryset = Stage.objects.all()
serializer_class = StageSerializer
def get_queryset(self):
return Stage.objects.select_subclasses()
class FlowStageBindingSerializer(ModelSerializer):
"""FlowStageBinding Serializer"""
stage_obj = StageSerializer(read_only=True, source="stage")
class Meta:
model = FlowStageBinding
fields = [
"pk",
"policybindingmodel_ptr_id",
"target",
"stage",
"stage_obj",
"evaluate_on_plan",
"re_evaluate_policies",
"order",
"policies",
]
class FlowStageBindingViewSet(ModelViewSet):
"""FlowStageBinding Viewset"""
queryset = FlowStageBinding.objects.all()
serializer_class = FlowStageBindingSerializer
filterset_fields = "__all__"
class FlowCacheViewSet(ListModelMixin, GenericViewSet):
"""Info about cached flows"""
queryset = Flow.objects.none()
serializer_class = Serializer
def list(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"pagination": {"count": len(cache.keys("flow_*"))}})

View File

@ -0,0 +1,66 @@
"""Flow Stage API Views"""
from django.urls import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.flows.api.flows import FlowSerializer
from authentik.flows.models import Stage
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class StageSerializer(ModelSerializer, MetaNameSerializer):
"""Stage Serializer"""
object_type = SerializerMethodField()
flow_set = FlowSerializer(many=True, required=False)
def get_object_type(self, obj: Stage) -> str:
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("stage", "")
class Meta:
model = Stage
fields = [
"pk",
"name",
"object_type",
"verbose_name",
"verbose_name_plural",
"flow_set",
]
class StageViewSet(ReadOnlyModelViewSet):
"""Stage Viewset"""
queryset = Stage.objects.all().select_related("flow_set")
serializer_class = StageSerializer
search_fields = ["name"]
filterset_fields = ["name"]
def get_queryset(self):
return Stage.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable stage types"""
data = []
for subclass in all_subclasses(self.queryset.model, False):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:stage-create")
+ f"?type={subclass.__name__}",
}
)
data = sorted(data, key=lambda x: x["name"])
return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -0,0 +1,109 @@
"""Challenge helpers"""
from enum import Enum
from typing import TYPE_CHECKING, Optional
from django.db.models.base import Model
from django.http import JsonResponse
from rest_framework.fields import ChoiceField, DictField
from rest_framework.serializers import CharField, Serializer
from authentik.flows.transfer.common import DataclassEncoder
if TYPE_CHECKING:
from authentik.flows.stage import StageView
class ChallengeTypes(Enum):
"""Currently defined challenge types"""
native = "native"
shell = "shell"
redirect = "redirect"
class ErrorDetailSerializer(Serializer):
"""Serializer for rest_framework's error messages"""
string = CharField()
code = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class Challenge(Serializer):
"""Challenge that gets sent to the client based on which stage
is currently active"""
type = ChoiceField(choices=list(ChallengeTypes))
component = CharField(required=False)
title = CharField(required=False)
response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=False, required=False
)
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class RedirectChallenge(Challenge):
"""Challenge type to redirect the client"""
to = CharField()
class ShellChallenge(Challenge):
"""Legacy challenge type to render HTML as-is"""
body = CharField()
class WithUserInfoChallenge(Challenge):
"""Challenge base which shows some user info"""
pending_user = CharField()
pending_user_avatar = CharField()
class PermissionSerializer(Serializer):
"""Permission used for consent"""
name = CharField()
id = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class ChallengeResponse(Serializer):
"""Base class for all challenge responses"""
stage: Optional["StageView"]
def __init__(self, instance, data, **kwargs):
self.stage = kwargs.pop("stage", None)
super().__init__(instance=instance, data=data, **kwargs)
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class HttpChallengeResponse(JsonResponse):
"""Subclass of JsonResponse that uses the `DataclassEncoder`"""
def __init__(self, challenge, **kwargs) -> None:
# pyright: reportGeneralTypeIssues=false
super().__init__(challenge.data, encoder=DataclassEncoder, **kwargs)

View File

@ -34,7 +34,8 @@ class FlowStageBindingForm(forms.ModelForm):
"""FlowStageBinding Form"""
stage = GroupedModelChoiceField(
queryset=Stage.objects.all().select_subclasses(), to_field_name="stage_uuid"
queryset=Stage.objects.all().order_by("name").select_subclasses(),
to_field_name="stage_uuid",
)
def __init__(self, *args, **kwargs):

View File

@ -43,7 +43,7 @@ class ReevaluateMarker(StageMarker):
engine = PolicyEngine(self.binding, self.user)
engine.use_cache = False
if http_request:
engine.request.http_request = http_request
engine.request.set_http_request(http_request)
engine.request.context = plan.context
engine.build()
result = engine.result

View File

@ -5,7 +5,7 @@ from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.flows.models import FlowDesignation
from authentik.stages.identification.models import Templates, UserFields
from authentik.stages.identification.models import UserFields
def create_default_authentication_flow(
@ -26,7 +26,6 @@ def create_default_authentication_flow(
name="default-authentication-identification",
defaults={
"user_fields": [UserFields.E_MAIL, UserFields.USERNAME],
"template": Templates.DEFAULT_LOGIN,
},
)
@ -50,21 +49,21 @@ def create_default_authentication_flow(
target=flow,
stage=identification_stage,
defaults={
"order": 0,
"order": 10,
},
)
FlowStageBinding.objects.using(db_alias).update_or_create(
target=flow,
stage=password_stage,
defaults={
"order": 1,
"order": 20,
},
)
FlowStageBinding.objects.using(db_alias).update_or_create(
target=flow,
stage=login_stage,
defaults={
"order": 2,
"order": 100,
},
)

View File

@ -23,7 +23,8 @@ class NotConfiguredAction(models.TextChoices):
"""Decides how the FlowExecutor should proceed when a stage isn't configured"""
SKIP = "skip"
# CONFIGURE = "configure"
DENY = "deny"
CONFIGURE = "configure"
class FlowDesignation(models.TextChoices):
@ -71,7 +72,7 @@ class Stage(SerializerModel):
def __str__(self):
if hasattr(self, "__in_memory_type"):
return f"In-memory Stage {getattr(self, '__in_memory_type')}"
return f"Stage {self.name}"
return self.name
def in_memory_stage(view: Type["StageView"]) -> Stage:
@ -118,7 +119,7 @@ class Flow(SerializerModel, PolicyBindingModel):
@property
def serializer(self) -> BaseSerializer:
from authentik.flows.api import FlowSerializer
from authentik.flows.api.flows import FlowSerializer
return FlowSerializer
@ -189,12 +190,12 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
@property
def serializer(self) -> BaseSerializer:
from authentik.flows.api import FlowStageBindingSerializer
from authentik.flows.api.bindings import FlowStageBindingSerializer
return FlowStageBindingSerializer
def __str__(self) -> str:
return f"{self.target} #{self.order} -> {self.stage}"
return f"{self.target} #{self.order}"
class Meta:

View File

@ -1,6 +1,6 @@
"""Flows Planner"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from django.core.cache import cache
from django.http import HttpRequest
@ -38,15 +38,20 @@ class FlowPlan:
flow_pk: str
stages: List[Stage] = field(default_factory=list)
context: Dict[str, Any] = field(default_factory=dict)
markers: List[StageMarker] = field(default_factory=list)
stages: list[Stage] = field(default_factory=list)
context: dict[str, Any] = field(default_factory=dict)
markers: list[StageMarker] = field(default_factory=list)
def append(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
self.stages.append(stage)
self.markers.append(marker or StageMarker())
def insert(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Insert stage into plan, as immediate next stage"""
self.stages.insert(1, stage)
self.markers.insert(1, marker or StageMarker())
def next(self, http_request: Optional[HttpRequest]) -> Optional[Stage]:
"""Return next pending stage from the bottom of the list"""
if not self.has_stages:
@ -96,7 +101,7 @@ class FlowPlanner:
self._logger = get_logger().bind(flow=flow)
def plan(
self, request: HttpRequest, default_context: Optional[Dict[str, Any]] = None
self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None
) -> FlowPlan:
"""Check each of the flows' policies, check policies for each stage with PolicyBinding
and return ordered list"""
@ -149,7 +154,7 @@ class FlowPlanner:
self,
user: User,
request: HttpRequest,
default_context: Optional[Dict[str, Any]],
default_context: Optional[dict[str, Any]],
) -> FlowPlan:
"""Build flow plan by checking each stage in their respective
order and checking the applied policies"""

View File

@ -1,45 +1,116 @@
"""authentik stage Base view"""
from collections import namedtuple
from typing import Any, Dict
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from django.http.request import QueryDict
from django.http.response import HttpResponse
from django.views.generic.base import View
from structlog.stdlib import get_logger
from authentik.core.models import DEFAULT_AVATAR, User
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
HttpChallengeResponse,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
FakeUser = namedtuple("User", ["username", "email"])
LOGGER = get_logger()
class StageView(TemplateView):
class StageView(View):
"""Abstract Stage, inherits TemplateView but can be combined with FormView"""
template_name = "login/form_with_user.html"
executor: FlowExecutorView
request: HttpRequest = None
def __init__(self, executor: FlowExecutorView):
def __init__(self, executor: FlowExecutorView, **kwargs):
self.executor = executor
super().__init__(**kwargs)
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
kwargs["title"] = self.executor.flow.title
# Either show the matched User object or show what the user entered,
# based on what the earlier stage (mostly IdentificationStage) set.
# _USER_IDENTIFIER overrides the first User, as PENDING_USER is used for
# other things besides the form display
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
kwargs["user"] = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
def get_pending_user(self) -> User:
"""Either show the matched User object or show what the user entered,
based on what the earlier stage (mostly IdentificationStage) set.
_USER_IDENTIFIER overrides the first User, as PENDING_USER is used for
other things besides the form display.
If no user is pending, returns request.user"""
if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context:
kwargs["user"] = FakeUser(
return User(
username=self.executor.plan.context.get(
PLAN_CONTEXT_PENDING_USER_IDENTIFIER
),
email="",
)
kwargs["primary_action"] = _("Continue")
return super().get_context_data(**kwargs)
if PLAN_CONTEXT_PENDING_USER in self.executor.plan.context:
return self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
return self.request.user
class ChallengeStageView(StageView):
"""Stage view which response with a challenge"""
response_class = ChallengeResponse
def get_response_instance(self, data: QueryDict) -> ChallengeResponse:
"""Return the response class type"""
return self.response_class(None, data=data, stage=self)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Return a challenge for the frontend to solve"""
challenge = self._get_challenge(*args, **kwargs)
if not challenge.is_valid():
LOGGER.warning(challenge.errors)
return HttpChallengeResponse(challenge)
# pylint: disable=unused-argument
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Handle challenge response"""
challenge: ChallengeResponse = self.get_response_instance(data=request.POST)
if not challenge.is_valid():
return self.challenge_invalid(challenge)
return self.challenge_valid(challenge)
def _get_challenge(self, *args, **kwargs) -> Challenge:
challenge = self.get_challenge(*args, **kwargs)
if "title" not in challenge.initial_data:
challenge.initial_data["title"] = self.executor.flow.title
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():
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:
"""Return the challenge that the client should solve"""
raise NotImplementedError
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
"""Callback when the challenge has the correct format"""
raise NotImplementedError
def challenge_invalid(self, response: ChallengeResponse) -> HttpResponse:
"""Callback when the challenge has the incorrect format"""
challenge_response = self._get_challenge()
full_errors = {}
for field, errors in response.errors.items():
for error in errors:
full_errors.setdefault(field, [])
full_errors[field].append(
{
"string": str(error),
"code": error.code,
}
)
challenge_response.initial_data["response_errors"] = full_errors
if not challenge_response.is_valid():
LOGGER.warning(challenge_response.errors)
return HttpChallengeResponse(challenge_response)

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