Compare commits

..

230 Commits

Author SHA1 Message Date
3e3f29973b release: 2021.1.4-stable 2021-01-29 10:29:06 +01:00
af7e1fd0c5 build(deps): bump rollup from 2.38.0 to 2.38.1 in /web (#505)
Bumps [rollup](https://github.com/rollup/rollup) from 2.38.0 to 2.38.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.38.0...v2.38.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-29 09:31:59 +01:00
2556a106a0 build(deps): bump boto3 from 1.16.61 to 1.16.62 (#504)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.61 to 1.16.62.
- [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.16.61...1.16.62)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-29 09:31:37 +01:00
2f3a086f29 docs: update veeam docs for group mapping 2021-01-28 23:34:51 +01:00
239af7048a providers/saml: import SAML Provider with all autogenerated mappings 2021-01-28 23:32:36 +01:00
188ef0f58f core: only cache Applications API when no filtering is done 2021-01-28 23:16:51 +01:00
5ef4354723 providers/saml: make NameID configurable using a Property Mapping 2021-01-28 22:50:13 +01:00
66a8b52c7c providers/saml: update default OIDs for default property mappings 2021-01-28 22:44:44 +01:00
c1563f4cff lib: fix ak_is_group_member checking wrong groups 2021-01-28 22:30:59 +01:00
ac7b0ac965 web: fix site-shell being cut off when not full height 2021-01-28 22:17:20 +01:00
da37b42bcf admin: fix providers not showing SAML Import on empty state 2021-01-28 22:16:50 +01:00
f4bb22138c providers/saml: add support for WindowsDomainQualifiedName, add docs for NameID 2021-01-28 22:00:40 +01:00
605213821c docs: add SAML docs for veeam enterprise manager 2021-01-28 21:20:28 +01:00
2b34ac7545 build(deps): bump @types/codemirror from 0.0.106 to 0.0.107 in /web (#503)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.106 to 0.0.107.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-28 10:04:36 +01:00
542a4b9bdf build(deps): bump @patternfly/patternfly from 4.70.2 to 4.80.3 in /web (#502)
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 4.70.2 to 4.80.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.70.2...prerelease-v4.80.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-28 10:04:15 +01:00
b0a791711e build(deps): bump boto3 from 1.16.60 to 1.16.61 (#501)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.60 to 1.16.61.
- [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.16.60...1.16.61)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-28 10:03:08 +01:00
c0199933c8 events: fix email template for notifications 2021-01-27 13:22:43 +01:00
5c3f410016 release: 2021.1.3-stable 2021-01-27 10:50:48 +01:00
02e4a71e25 Merge branch 'master' into version-2021.1 2021-01-27 10:50:41 +01:00
bfe8bb5e61 lifecycle: fix typo causing single process in docker-compose 2021-01-27 10:13:23 +01:00
b1591618ae admin: handle FlowNonApplicableException during flow plan 2021-01-27 09:57:26 +01:00
55bcc254c1 flows: fix FlowNonApplicableException not being Sentry Ignored 2021-01-27 09:57:18 +01:00
2798a3edc9 build(deps): bump boto3 from 1.16.59 to 1.16.60 (#498)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.59 to 1.16.60.
- [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.16.59...1.16.60)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-27 09:05:00 +01:00
e2aaa26ce7 build(deps): bump urllib3 from 1.26.2 to 1.26.3 (#499)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.2 to 1.26.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/1.26.3/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.2...1.26.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-27 09:04:42 +01:00
81e4d2d1d7 build(deps-dev): bump coverage from 5.3.1 to 5.4 (#500)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.3.1 to 5.4.
- [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.3.1...coverage-5.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-27 09:04:18 +01:00
f663b66c19 docs: fix nextcloud docs using wrong fields 2021-01-26 22:10:00 +01:00
9a7b343120 build(deps-dev): bump @typescript-eslint/parser in /web (#495)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.14.0 to 4.14.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.14.1/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 10:07:53 +01:00
02c1a7f7d0 build(deps): bump @sentry/browser from 6.0.1 to 6.0.2 in /web (#496)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.0.1 to 6.0.2.
- [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.0.1...6.0.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 10:07:27 +01:00
b2f65a7ed2 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#497)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.14.0 to 4.14.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.14.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-01-26 10:05:40 +01:00
8071692739 build(deps): bump @sentry/tracing from 6.0.1 to 6.0.2 in /web (#494)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.0.1 to 6.0.2.
- [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.0.1...6.0.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 10:05:17 +01:00
8d11934caa build(deps-dev): bump pytest from 6.2.1 to 6.2.2 (#493)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.1...6.2.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-26 10:04:42 +01:00
6076ae2f9e ci: fix building for dependabot 2021-01-25 21:11:29 +01:00
78b4b61882 build(deps): bump boto3 from 1.16.58 to 1.16.59 (#489) 2021-01-25 13:59:42 +01:00
91df37a4a0 build(deps): bump ldap3 from 2.8.1 to 2.9 (#490) 2021-01-25 13:58:20 +01:00
2566af231b build(deps): bump @types/chart.js from 2.9.29 to 2.9.30 in /web (#491) 2021-01-25 13:57:23 +01:00
80f7b5656d build(deps): bump rollup from 2.37.1 to 2.38.0 in /web (#492) 2021-01-25 13:56:58 +01:00
23cb8f44a6 build(deps): bump @sentry/tracing from 6.0.0 to 6.0.1 in /web (#487)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.0.0 to 6.0.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.0.0...6.0.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-22 09:47:24 +01:00
c3a0aa594a build(deps): bump boto3 from 1.16.57 to 1.16.58 (#486)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.57 to 1.16.58.
- [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.16.57...1.16.58)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-22 09:45:52 +01:00
6b7977ad86 build(deps): bump @sentry/browser from 6.0.0 to 6.0.1 in /web (#488)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.0.0 to 6.0.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.0.0...6.0.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-22 09:45:37 +01:00
d7dfd6e7df build(deps): bump pyyaml from 5.4 to 5.4.1 (#484)
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4 to 5.4.1.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.4...5.4.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-21 09:53:20 +01:00
fc5842be67 build(deps): bump rollup from 2.37.0 to 2.37.1 in /web (#485)
Bumps [rollup](https://github.com/rollup/rollup) from 2.37.0 to 2.37.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.37.0...v2.37.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-21 09:53:08 +01:00
b03677a077 build(deps): bump codemirror from 5.59.1 to 5.59.2 in /web (#483)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.59.1 to 5.59.2.
- [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.1...5.59.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-21 09:52:54 +01:00
d136890415 build(deps): bump @sentry/browser from 5.30.0 to 6.0.0 in /web (#478)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.30.0 to 6.0.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/5.30.0...6.0.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-20 09:54:50 +01:00
3ea76f1d86 build(deps): bump rollup from 2.36.2 to 2.37.0 in /web (#479)
Bumps [rollup](https://github.com/rollup/rollup) from 2.36.2 to 2.37.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.36.2...v2.37.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-20 09:39:19 +01:00
1ab9683ec6 build(deps): bump @sentry/tracing from 5.30.0 to 6.0.0 in /web (#480)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 5.30.0 to 6.0.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/5.30.0...6.0.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-20 09:39:00 +01:00
1e16c9b1e8 build(deps): bump boto3 from 1.16.56 to 1.16.57 (#482)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.56 to 1.16.57.
- [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.16.56...1.16.57)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-20 09:38:40 +01:00
b242ba03a0 build(deps): bump pyyaml from 5.3.1 to 5.4 (#481)
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3.1 to 5.4.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.3.1...5.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-20 09:38:21 +01:00
49294b4a43 build(deps-dev): bump @typescript-eslint/parser in /web (#477)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.13.0 to 4.14.0.
- [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.14.0/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-19 09:34:07 +01:00
80e5c25c01 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#476)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.13.0 to 4.14.0.
- [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.14.0/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-19 09:22:15 +01:00
ed267a4a1a docs: fix release name 2021-01-18 11:23:09 +01:00
7d844d1821 release: 2021.1.2-stable 2021-01-18 11:15:11 +01:00
6f1fb9ca43 release: 2021.1.2-stable 2021-01-18 11:14:55 +01:00
09f56f1f01 Merge branch 'master' into version-2021.1 2021-01-18 11:14:45 +01:00
3d3a0cd9e3 events: create event when system task fails 2021-01-18 10:09:14 +01:00
32667f37d1 build(deps): bump boto3 from 1.16.55 to 1.16.56 (#473)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.55 to 1.16.56.
- [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.16.55...1.16.56)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-18 07:46:37 +01:00
9532c4df9d build(deps-dev): bump eslint from 7.17.0 to 7.18.0 in /web (#474)
Bumps [eslint](https://github.com/eslint/eslint) from 7.17.0 to 7.18.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.17.0...v7.18.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-18 07:46:26 +01:00
fd90979832 build(deps): bump rollup from 2.36.1 to 2.36.2 in /web (#475)
Bumps [rollup](https://github.com/rollup/rollup) from 2.36.1 to 2.36.2.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.36.1...v2.36.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-18 07:46:14 +01:00
2e20d5dfbf stages/email: fix email task not falling back to use_global_settings 2021-01-17 23:32:55 +01:00
33f06f0799 policies: fix logic error for sync mode 2021-01-17 23:32:55 +01:00
920736fc77 helm: fix s3 secret key and email password not being base64 encoded 2021-01-17 23:32:55 +01:00
ee8e42728e helm: fix old reference to static secret_key 2021-01-17 23:32:55 +01:00
204792b750 stages/email: fix email task not falling back to use_global_settings 2021-01-17 23:31:58 +01:00
8ffa3e5885 policies: fix logic error for sync mode 2021-01-17 23:31:34 +01:00
175d3b3377 helm: fix s3 secret key and email password not being base64 encoded 2021-01-17 23:02:14 +01:00
d5f35798dc helm: fix old reference to static secret_key 2021-01-17 23:01:58 +01:00
1a0aa7e944 Merge branch 'version-2021.1'
# Conflicts:
#	.bumpversion.cfg
#	.github/workflows/release.yml
#	authentik/__init__.py
#	docker-compose.yml
#	helm/Chart.yaml
#	helm/README.md
#	helm/values.yaml
#	outpost/pkg/version.go
#	web/src/constants.ts
#	website/docs/installation/docker-compose.md
#	website/docs/installation/kubernetes.md
2021-01-17 22:37:13 +01:00
677a181b9c release: 2021.1.1-stable 2021-01-17 22:36:16 +01:00
4b551add1a stages/password: catch importerror during authentic() 2021-01-17 20:23:22 +01:00
90220e911f stages/password: catch importerror during authentic() 2021-01-17 20:18:45 +01:00
217cca822d web: fix sidebar overlaying background 2021-01-17 20:09:53 +01:00
e6f897c7e6 policies: detect when running in a daemon process and run policies sync 2021-01-17 20:09:53 +01:00
65c9d4bf4c policies: use custom context for fork instead of changing global context 2021-01-17 20:09:53 +01:00
6e88e52d78 outposts: add message to outpost_service_connection_monitor task 2021-01-17 20:09:53 +01:00
4e884e80ab web: fix sidebar overlaying background 2021-01-17 20:09:37 +01:00
d19bfebce3 policies: detect when running in a daemon process and run policies sync 2021-01-17 19:59:58 +01:00
b86d4a455d policies: use custom context for fork instead of changing global context 2021-01-17 19:59:19 +01:00
222cece3e1 outposts: add message to outpost_service_connection_monitor task 2021-01-17 19:22:01 +01:00
6e69edf1af core: increase application cache duration
# Conflicts:
#	authentik/core/api/applications.py
2021-01-17 19:17:47 +01:00
55aab5660b core: increase application cache duration 2021-01-17 19:17:13 +01:00
08e7ef3c1e core: increase application cache duration 2021-01-17 19:04:54 +01:00
d728163eea helm: fix typos 2021-01-17 18:56:51 +01:00
cbf246694c helm: fix typos 2021-01-17 18:56:24 +01:00
9d0a01012d root: use stable version on master
This reverts commit 94182f88a4.
2021-01-17 17:41:49 +01:00
cf76652a4c release: 2021.1.1-rc2 2021-01-17 17:40:43 +01:00
c525ecc334 ci: fix paths for github release 2021-01-17 17:40:20 +01:00
49d40d4337 admin: fix linting 2021-01-17 17:35:00 +01:00
94182f88a4 release: 2021.1.1-rc1 2021-01-17 17:25:47 +01:00
1c25f4f09b core: use tabs for user settings 2021-01-17 17:25:15 +01:00
6495d6c50a web: fix tabs detecting all slot elements 2021-01-16 23:04:08 +01:00
b81f3e4a38 docs: prepare next release 2021-01-16 22:38:46 +01:00
aad3b43ac3 core: cache applications API 2021-01-16 22:38:09 +01:00
60f52f102a outposts: optimise signals to not always trigger 2021-01-16 22:14:37 +01:00
f3ccb5341d outpost: improve logging 2021-01-16 22:13:57 +01:00
cb73210447 events: don't log permission creation 2021-01-16 22:03:06 +01:00
81efc9a673 outposts: update pipeline 2021-01-16 21:49:30 +01:00
72c6c0da9b outposts: update go module domain 2021-01-16 21:45:24 +01:00
8fef839965 outpost: separate ak-api and proxy further for future outposts 2021-01-16 21:41:39 +01:00
87b830ff9a outpost: rename proxy to outpost 2021-01-16 19:20:27 +01:00
8acb9dde5f web: add notification 'mark as seen' button 2021-01-16 19:09:03 +01:00
36e8b1004c web: add update method 2021-01-16 19:08:20 +01:00
f959212692 events: make notifications filterable 2021-01-16 19:08:07 +01:00
2d2a404028 providers/oauth2: improve error handling and event creation 2021-01-16 18:27:10 +01:00
394ad6ade5 web: wrap notifications desc 2021-01-16 18:22:25 +01:00
4baf9e4a22 web: fix unread count, use white-space pre 2021-01-16 18:04:09 +01:00
d020599e09 web: format json output for EventInfo 2021-01-16 17:02:31 +01:00
4f28a89e63 policies: improve recording of error messages during policy process 2021-01-16 16:38:57 +01:00
f8b4b92e8d policies: pass direct exception from expression policies 2021-01-16 15:41:59 +01:00
33f208657c helm: bump memory hard limit 2021-01-16 14:15:50 +01:00
c1fbfc63ab core: use version in qs for static files to ensure latest are loaded 2021-01-16 14:15:42 +01:00
192dbe05c4 events: triggers -> rules 2021-01-16 14:15:23 +01:00
0b41cb84f0 web: update styling of notification drawer to match rest 2021-01-15 12:16:35 +01:00
d637bd0bf9 events: improve infinite loop detection 2021-01-15 11:27:18 +01:00
a2bddc6d91 policies: fix engine tests checking wrong key 2021-01-15 11:27:07 +01:00
2e42da11ea policies/event_matcher: simplify validity checking 2021-01-15 11:26:55 +01:00
f297d1256d events: fix linting 2021-01-15 11:19:56 +01:00
5e1e5afb24 build(deps): bump boto3 from 1.16.54 to 1.16.55 (#472) 2021-01-15 09:26:35 +01:00
da59e7c4a7 events: fix infinite loop in unittests 2021-01-15 00:32:59 +01:00
8684d106d5 events: fix default transport for successive migrations 2021-01-14 23:50:18 +01:00
2579e168c3 web: start implementing notifications 2021-01-14 22:14:35 +01:00
7f5caf901d expressions: set exception as message field 2021-01-14 21:58:10 +01:00
1c686e19b5 policies: set message instead of error for Event 2021-01-14 20:17:21 +01:00
3cc92f6c97 events: ensure created field is timestamp 2021-01-14 20:16:54 +01:00
8f5b33a3a2 web: show more context on policy_exception 2021-01-14 18:13:52 +01:00
4447345345 policies: fix display of stacktrace in events 2021-01-14 18:07:41 +01:00
42c6401ba7 events: add event context to slack webhook 2021-01-14 17:40:19 +01:00
eef111bcfd events: disable policy cache for trigger 2021-01-14 17:39:59 +01:00
6192b2787f events: notifications: send entire event in API 2021-01-14 17:22:02 +01:00
c7d28f8ca9 events: attach default transport to default triggers 2021-01-14 17:22:02 +01:00
1342266368 events: include full group in event notification 2021-01-14 17:22:02 +01:00
7ff679b1a3 policies: fix error when error occurs during policy process with no target 2021-01-14 17:22:02 +01:00
8beddcddb0 events: set severity for default triggers 2021-01-14 17:22:02 +01:00
9fe8554f28 events: make notification read/update only 2021-01-14 17:22:02 +01:00
812fe72e60 build(deps): bump @sentry/tracing from 5.29.2 to 5.30.0 in /web (#470)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 5.29.2 to 5.30.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/5.29.2...5.30.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-14 10:00:29 +01:00
d0e4533cdd build(deps): bump boto3 from 1.16.53 to 1.16.54 (#468)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.53 to 1.16.54.
- [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.16.53...1.16.54)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-14 09:22:22 +01:00
b1b5d94ddc build(deps): bump @fortawesome/fontawesome-free in /web (#471)
Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 5.15.1 to 5.15.2.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/master/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/5.15.1...5.15.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-14 09:22:04 +01:00
59722e0bbe build(deps): bump @sentry/browser from 5.29.2 to 5.30.0 in /web (#469)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.29.2 to 5.30.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/5.29.2...5.30.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-14 09:21:46 +01:00
9c5bb3998c docs: fix minor grammar issues 2021-01-13 10:28:02 +01:00
c180c4b1a2 build(deps): bump boto3 from 1.16.52 to 1.16.53 (#467)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.52 to 1.16.53.
- [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.16.52...1.16.53)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-13 09:29:14 +01:00
308896719d docs: add docs for events and notifications 2021-01-13 00:26:33 +01:00
95c1473dd2 events: assign default triggers to default admin group, create default transport 2021-01-12 23:28:17 +01:00
b14c5039ed events: set default admin group to receive default triggers 2021-01-12 23:06:42 +01:00
b6948334f2 policies/event_matcher: fix verbose_name 2021-01-12 23:06:24 +01:00
29e08e7477 stages/otp_*: fix app's verbose_name 2021-01-12 22:59:46 +01:00
36bc1dc020 events: record source when user is using source to authenticate 2021-01-12 22:48:55 +01:00
61d1407804 sources/*: Set PLAN_CONTEXT_SOURCE when logging in with a source 2021-01-12 22:37:33 +01:00
47ddf0d7f2 web: add UI for notification triggers 2021-01-12 22:26:57 +01:00
cb36a3c8c7 web: add event transports UI 2021-01-12 22:12:16 +01:00
cac94792fa admin: add event transport forms 2021-01-12 22:03:33 +01:00
6f56c37d2f web: make action button better handle errors and show messages 2021-01-12 21:52:21 +01:00
8369fa16ae events: add mode_verbose to transport, return string on send error 2021-01-12 21:51:55 +01:00
f30bdbecd6 events: catch errors during send and re-raise as custom type 2021-01-12 21:48:16 +01:00
c727c845df policies: add and/or mode (#463)
* policies: add mode to PolicyEngine for AND and OR modes

* events: use PolicyEngine in OR mode
2021-01-12 18:22:25 +01:00
b2b737e59e build(deps-dev): bump @typescript-eslint/parser in /web (#465)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.12.0 to 4.13.0.
- [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.13.0/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-12 10:16:47 +01:00
e2b930afe3 build(deps): bump boto3 from 1.16.51 to 1.16.52 (#464)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.51 to 1.16.52.
- [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.16.51...1.16.52)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-12 09:45:24 +01:00
36c0b924bc build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#466)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.12.0 to 4.13.0.
- [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.13.0/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-12 09:45:14 +01:00
1ccf6dcf6f events: Notifications (#418)
* events: initial alerting implementation

* policies: move error handling to process, ensure policy UUID is saved

* policies: add tests for error handling in PolicyProcess

* events: improve loop detection

* events: add API for action and trigger

* policies: ensure http_request is not used in context

* events: adjust unittests for user handling

* policies/event_matcher: add policy type

* events: add API tests

* events: add middleware tests

* core: make application's provider not required

* outposts: allow blank kubeconfig

* outposts: validate kubeconfig before saving

* api: fix formatting

* stages/invitation: remove invitation_created signal as model_created functions the same

* stages/invitation: ensure created_by is set when creating from API

* events: rebase migrations on master

* events: fix missing Alerts from API

* policies: fix unittests

* events: add tests for alerts

* events: rename from alerting to notifications

* events: add ability to specify severity of notification created

* policies/event_matcher: Add app field to match on event app

* policies/event_matcher: fix EventMatcher not being included in API

* core: use objects.none() when get_queryset is used

* events: use m2m for multiple transports, create notification object in task

* events: add default triggers

* events: fix migrations return value

* events: fix notification_transport not being in the correct queue

* stages/email: allow sending of email without backend

* events: implement sending via webhook + slack/discord + email
2021-01-11 18:43:59 +01:00
f8a426f0e8 ci: fix docker tag name for dependabot PRs 2021-01-11 16:18:44 +01:00
f8756d0fc9 build(deps): bump @types/codemirror from 0.0.105 to 0.0.106 in /web (#462)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.105 to 0.0.106.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-11 08:23:19 +01:00
fd6d99f4f9 build(deps): bump @types/codemirror from 0.0.104 to 0.0.105 in /web (#461)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.104 to 0.0.105.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-08 09:52:17 +01:00
04379f2c90 build(deps): bump boto3 from 1.16.50 to 1.16.51 (#460)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.50 to 1.16.51.
- [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.16.50...1.16.51)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-08 09:52:02 +01:00
ba1195cf70 build(deps): bump boto3 from 1.16.49 to 1.16.50 (#458)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.49 to 1.16.50.
- [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.16.49...1.16.50)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-07 10:09:08 +01:00
b0bd9212c7 build(deps): bump rollup from 2.36.0 to 2.36.1 in /web (#459)
Bumps [rollup](https://github.com/rollup/rollup) from 2.36.0 to 2.36.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.36.0...v2.36.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-07 10:08:48 +01:00
209179e012 build(deps): bump rollup from 2.35.1 to 2.36.0 in /web (#456)
Bumps [rollup](https://github.com/rollup/rollup) from 2.35.1 to 2.36.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.35.1...v2.36.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 14:01:26 +01:00
df16f635fa build(deps): bump boto3 from 1.16.48 to 1.16.49 (#457)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.48 to 1.16.49.
- [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.16.48...1.16.49)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 14:01:03 +01:00
14ccf47a2b build(deps): bump tslib from 2.0.3 to 2.1.0 in /web (#455)
Bumps [tslib](https://github.com/Microsoft/tslib) from 2.0.3 to 2.1.0.
- [Release notes](https://github.com/Microsoft/tslib/releases)
- [Commits](https://github.com/Microsoft/tslib/compare/2.0.3...2.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 14:00:40 +01:00
2aac024477 build(deps): bump @types/codemirror from 0.0.103 to 0.0.104 in /web (#454)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.103 to 0.0.104.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 14:00:25 +01:00
4743e72e18 policies: ensure binding has a target during unittests 2021-01-05 12:37:52 +01:00
cab2942c4e web: fix [object Object] being shown for policy_execution
closes #449
2021-01-05 11:52:25 +01:00
9fb5ce2a1a policies: add binding to policy_execution context 2021-01-05 11:51:05 +01:00
0eab4489c5 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#453)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.11.1 to 4.12.0.
- [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.12.0/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 10:41:48 +01:00
3aae030b23 build(deps): bump boto3 from 1.16.47 to 1.16.48 (#450)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.47 to 1.16.48.
- [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.16.47...1.16.48)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 10:41:35 +01:00
e7060cb90a build(deps-dev): bump @typescript-eslint/parser in /web (#452)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.11.1 to 4.12.0.
- [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.12.0/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 09:47:01 +01:00
6c0b9e3525 build(deps): bump django from 3.1.4 to 3.1.5 (#451)
Bumps [django](https://github.com/django/django) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.4...3.1.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 09:46:46 +01:00
82bb179bc2 root: global email settings (#448)
* root: make global email settings configurable

* stages/email: add use_global_settings

* stages/email: add test_email command to test email sending

* stages/email: update email template

* stages/email: simplify email template path

* stages/email: add support for user-supplied email templates

* stages/email: add tests for sending and templates

* stages/email: only add custom template if permissions are correct

* docs: add custom email template docs

* root: add /templates volume in docker-compose by default

* stages/email: fix form not allowing custom templates

* stages/email: use relative path for custom templates

* stages/email: check if all templates exist on startup, reset

* docs: add global email docs for docker-compose

* helm: add email config to helm chart

* helm: load all secrets with env prefix

* helm: move s3 and smtp secret to secret

* stages/email: fix test for relative name

* stages/email: add argument to send email from existing stage

* stages/email: set uid using slug of message id

* stages/email: ensure template validation ignores migration runs

* docs: add email troubleshooting docs

* stages/email: fix long task_name breaking task list
2021-01-05 00:41:10 +01:00
774eb0388b build(deps-dev): bump eslint from 7.16.0 to 7.17.0 in /web (#447)
Bumps [eslint](https://github.com/eslint/eslint) from 7.16.0 to 7.17.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.16.0...v7.17.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-04 08:56:28 +01:00
6ed78830a0 providers/proxy: check ingress annotations we manage 2021-01-02 01:48:39 +01:00
6fe323f1a7 outposts: by default only check labels 2021-01-02 01:08:05 +01:00
85c2db018e outposts: ensure field_manager is also used for updates 2021-01-02 00:52:42 +01:00
bc9e7e8b93 build(deps): bump structlog from 20.1.0 to 20.2.0 (#445)
* build(deps): bump structlog from 20.1.0 to 20.2.0

Bumps [structlog](https://github.com/hynek/structlog) from 20.1.0 to 20.2.0.
- [Release notes](https://github.com/hynek/structlog/releases)
- [Changelog](https://github.com/hynek/structlog/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/hynek/structlog/compare/20.1.0...20.2.0)

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

* *: use structlog.stdlib instead of structlog for type-hints

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-01-01 15:39:43 +01:00
08c58ce3fb build(deps): bump boto3 from 1.16.46 to 1.16.47 (#444)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.46 to 1.16.47.
- [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.16.46...1.16.47)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-01 13:51:17 +01:00
c3bc986473 build(deps): bump codemirror from 5.59.0 to 5.59.1 in /web (#446)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.59.0 to 5.59.1.
- [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.0...5.59.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-01 13:51:00 +01:00
2e69efe699 providers/saml: sign metadata when signing is enabled 2020-12-31 15:02:21 +01:00
4daa373dcf web: match on path begin in sidebar 2020-12-31 14:19:36 +01:00
a85b8a65c0 release: 0.14.2-stable 2020-12-31 12:00:31 +01:00
d8dc1f8bb5 web: fix linting 2020-12-31 11:34:30 +01:00
0f4d5bc3b0 web: fix colour for selected sortable table header 2020-12-31 11:31:49 +01:00
6eed549577 web: add EventInfo case for policy_execution 2020-12-31 11:21:52 +01:00
be54ba4fe2 policies: catch error in process to not hang requests 2020-12-31 11:16:17 +01:00
68b9c34f78 policies: fix obj not being set 2020-12-31 11:16:01 +01:00
3584bdf530 events: fix error when creating an even from policyrequests 2020-12-31 11:15:42 +01:00
e712719333 admin: fix reverse urls for application forms 2020-12-31 10:13:06 +01:00
9a21c2f6bd build(deps): bump boto3 from 1.16.45 to 1.16.46 (#443)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.45 to 1.16.46.
- [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.16.45...1.16.46)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-31 08:55:06 +01:00
0632d8ff37 web: fix loading overlay for site-shell 2020-12-30 23:12:55 +01:00
6bfaf71c12 web: fix link on application list 2020-12-30 22:50:27 +01:00
b6c8c319e5 web: default to open sidebar 2020-12-30 22:45:00 +01:00
4fde1b7365 providers/saml: allow audience to be empty 2020-12-30 22:15:28 +01:00
412f5b9210 providers/saml: fix signing and verification kp not being set correctly 2020-12-30 22:11:24 +01:00
a9e53cd52a providers/saml: fix string being passed to lxml 2020-12-30 22:03:01 +01:00
d0ee7908ab providers/saml: force user to select authz flow for import 2020-12-30 22:02:41 +01:00
e69834dec4 providers/saml: show error message why import failed 2020-12-30 22:02:28 +01:00
1b9d22615c build(deps): bump construct-style-sheets-polyfill in /web (#442)
Bumps [construct-style-sheets-polyfill](https://github.com/calebdwilliams/adoptedStyleSheets) from 2.4.3 to 2.4.6.
- [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.3...v2.4.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-30 09:23:02 +01:00
e995536a15 build(deps): bump boto3 from 1.16.44 to 1.16.45 (#440)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.44 to 1.16.45.
- [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.16.44...1.16.45)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-30 09:22:52 +01:00
e6818faab1 build(deps): bump uvicorn from 0.13.2 to 0.13.3 (#441)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.13.2 to 0.13.3.
- [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.2...0.13.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-30 09:22:42 +01:00
010e834149 root: update security.md 2020-12-29 21:34:34 +01:00
16d5e1d9ff release: 0.14.1-stable 2020-12-29 21:25:49 +01:00
765ae80698 providers/oauth2: fix error when creating RefreshToken 2020-12-29 21:22:49 +01:00
bbd0ff24d8 docs: add grafana integration docs 2020-12-29 20:43:27 +01:00
7a403613b2 Suggested changes (#434)
L112 - L115 is a suggestion I'm unsure of, but wanted to somehow improve it. Feel free to discard, but I still recommend signup>sign-up on L114 of origin.

On L124, the resulting webpage shows "Active- Directory" (note the space after the hyphen). Not sure how to fix this though.
2020-12-29 20:17:35 +01:00
4ad184a3fb build(deps-dev): bump @typescript-eslint/parser in /web (#439)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.11.0 to 4.11.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.11.1/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-29 14:53:40 +01:00
48d5f28e7a build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#438)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.11.0 to 4.11.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.11.1/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-29 14:43:30 +01:00
0cb48121b2 build(deps): bump boto3 from 1.16.43 to 1.16.44 (#436)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.43 to 1.16.44.
- [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.16.43...1.16.44)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-29 13:41:19 +01:00
4194ffe2d4 build(deps): bump channels from 3.0.2 to 3.0.3 (#437)
Bumps [channels](https://github.com/django/channels) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/django/channels/releases)
- [Changelog](https://github.com/django/channels/blob/master/CHANGELOG.txt)
- [Commits](https://github.com/django/channels/compare/3.0.2...3.0.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-29 13:34:10 +01:00
4636fe7e64 Add migrate command to 0.14 docs. (#435) 2020-12-28 21:51:22 +01:00
182d714b16 docs: fix typo 2020-12-28 18:15:32 +01:00
540c22ce15 release: 0.14.0-stable 2020-12-28 17:49:45 +01:00
8c3008abce release: 0.14.0-rc2 2020-12-28 17:49:39 +01:00
8a22c86aaa release: 0.14.0-rc1 2020-12-28 17:49:35 +01:00
22ce142cb8 outposts: include protocol in outpost deployment ports 2020-12-28 17:21:02 +01:00
1a292feebb outposts: always check metadata on reconcile 2020-12-28 17:11:37 +01:00
09f4d812b3 outposts: set field_manager 2020-12-28 17:11:33 +01:00
2bab4ebfe8 core: fix library url pattern not matching SPA 2020-12-28 15:06:25 +01:00
a8647caca9 build(deps): bump @types/codemirror from 0.0.102 to 0.0.103 in /web (#433)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.102 to 0.0.103.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-28 14:32:57 +01:00
590597caf6 events: replace list view with SPA Page 2020-12-28 14:32:34 +01:00
7b43777b22 web: reset expanded rows on fetch 2020-12-28 14:04:07 +01:00
77861b52e3 web: fix search loading old results when using enter 2020-12-28 13:56:33 +01:00
5f9c1e229c root: return API dates as timestamp 2020-12-28 13:07:49 +01:00
119adb3e7b web: fix old default URL 2020-12-28 13:07:35 +01:00
5db38bd0b7 web: lazy-render expanded table 2020-12-28 13:07:20 +01:00
0e1587bc1a providers/oauth2: don't write authorization code to event log 2020-12-28 01:07:18 +01:00
dc16a8a4c9 providers/proxy: set proxy-size for nginx for larger response 2020-12-28 00:45:58 +01:00
299 changed files with 6309 additions and 1779 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.14.0-rc1
current_version = 2021.1.4-stable
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
@ -31,6 +31,6 @@ values =
[bumpversion:file:authentik/__init__.py]
[bumpversion:file:proxy/pkg/version.go]
[bumpversion:file:outpost/pkg/version.go]
[bumpversion:file:web/src/constants.ts]

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/authentik:0.14.0-rc1
-t beryju/authentik:2021.1.4-stable
-t beryju/authentik:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:0.14.0-rc1
run: docker push beryju/authentik:2021.1.4-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest
build-proxy:
@ -34,7 +34,7 @@ jobs:
go-version: "^1.15"
- name: prepare go api client
run: |
cd proxy
cd outpost
go get -u github.com/go-swagger/go-swagger/cmd/swagger
swagger generate client -f ../swagger.yaml -A authentik -t pkg/
go build -v .
@ -45,14 +45,14 @@ jobs:
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: |
cd proxy/
cd outpost/
docker build \
--no-cache \
-t beryju/authentik-proxy:0.14.0-rc1 \
-t beryju/authentik-proxy:2021.1.4-stable \
-t beryju/authentik-proxy:latest \
-f Dockerfile .
-f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:0.14.0-rc1
run: docker push beryju/authentik-proxy:2021.1.4-stable
- 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:0.14.0-rc1 \
-t beryju/authentik-static:2021.1.4-stable \
-t beryju/authentik-static:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:0.14.0-rc1
run: docker push beryju/authentik-static:2021.1.4-stable
- 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: 0.14.0-rc1
tagName: 2021.1.4-stable
environment: beryjuorg-prod

375
Pipfile.lock generated
View File

@ -25,10 +25,10 @@
},
"amqp": {
"hashes": [
"sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a",
"sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"
"sha256:1e759a7f202d910939de6eca45c23a107f6b71111f41d1282c648e9ac3d21901",
"sha256:affdd263d8b8eb3c98170b78bf83867cdb6a14901d586e00ddb65bfe2f0c4e60"
],
"version": "==5.0.2"
"version": "==5.0.5"
},
"asgiref": {
"hashes": [
@ -74,25 +74,25 @@
},
"boto3": {
"hashes": [
"sha256:0bb2c3159b9f5e0df50430bf06a155bd7f27f480825b6374dde807d42360a668",
"sha256:a49b3ab4bfa2f6394ba60165cfc468410797dd410f32eed47e22f61451ee986e"
"sha256:a280123db79e73478bd23933486f3a0ffa2397d1a6381f32573f2731ff48c59a",
"sha256:bb91fecf982e1bbfb68bb6bd2c9a0cce3c84ac6f97dd338d1ef9e47780679091"
],
"index": "pypi",
"version": "==1.16.43"
"version": "==1.16.62"
},
"botocore": {
"hashes": [
"sha256:7398c900dbd4e3d61647269215396ea3e8082f494f3e7b65d9b6aca049c1d463",
"sha256:795a67338cadb0c3a45014a6c81659da6af623a4e973812f87a6f9d9fb7712e9"
"sha256:1046c152e5865aabbe6b10b2d33e652b3dd072516f3976e96cacc6b7c4460d02",
"sha256:29b4b9be5b40f392a033926c08c004c01bd6471384ef6f12eaa49ee3870a010c"
],
"version": "==1.19.43"
"version": "==1.19.62"
},
"cachetools": {
"hashes": [
"sha256:3796e1de094f0eaca982441c92ce96c68c89cced4cd97721ab297ea4b16db90e",
"sha256:c6b07a6ded8c78bf36730b3dc452dfff7d95f2a12a2fed856b1a0cb13ca78c61"
"sha256:1d9d5f567be80f7c07d765e21b814326d78c61eb0c3a637dffc0e5d1796cb2e2",
"sha256:f469e29e7aa4cff64d8de4aad95ce76de8ea1125a16c68e0d93f65c3c3dc92e9"
],
"version": "==4.2.0"
"version": "==4.2.1"
},
"celery": {
"hashes": [
@ -152,11 +152,11 @@
},
"channels": {
"hashes": [
"sha256:74db79c9eca616be69d38013b22083ab5d3f9ccda1ab5e69096b1bb7da2d9b18",
"sha256:f50a6e79757a64c1e45e95e144a2ac5f1e99ee44a0718ab182c501f5e5abd268"
"sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f",
"sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"
],
"index": "pypi",
"version": "==3.0.2"
"version": "==3.0.3"
},
"channels-redis": {
"hashes": [
@ -265,11 +265,11 @@
},
"django": {
"hashes": [
"sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2",
"sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
],
"index": "pypi",
"version": "==3.1.4"
"version": "==3.1.5"
},
"django-cors-middleware": {
"hashes": [
@ -351,7 +351,8 @@
},
"djangorestframework": {
"hashes": [
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7"
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7",
"sha256:0898182b4737a7b584a2c73735d89816343369f259fea932d90dc78e35d8ac33"
],
"index": "pypi",
"version": "==3.12.2"
@ -411,10 +412,10 @@
},
"h11": {
"hashes": [
"sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
"sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
],
"version": "==0.11.0"
"version": "==0.12.0"
},
"hiredis": {
"hashes": [
@ -486,10 +487,10 @@
},
"hyperlink": {
"hashes": [
"sha256:47fcc7cd339c6cb2444463ec3277bdcfe142c8b1daf2160bdd52248deec815af",
"sha256:c528d405766f15a2c536230de7e160b65a08e20264d8891b3eb03307b0df3c63"
"sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b",
"sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"
],
"version": "==20.0.1"
"version": "==21.0.0"
},
"idna": {
"hashes": [
@ -557,11 +558,11 @@
},
"ldap3": {
"hashes": [
"sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0"
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
],
"index": "pypi",
"version": "==2.8.1"
"version": "==2.9"
},
"lxml": {
"hashes": [
@ -701,10 +702,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
],
"version": "==3.0.8"
"version": "==3.0.14"
},
"psycopg2-binary": {
"hashes": [
@ -899,29 +900,37 @@
},
"pytz": {
"hashes": [
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
],
"version": "==2020.4"
"version": "==2020.5"
},
"pyyaml": {
"hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
],
"index": "pypi",
"version": "==5.3.1"
"version": "==5.4.1"
},
"qrcode": {
"hashes": [
@ -955,11 +964,11 @@
},
"rsa": {
"hashes": [
"sha256:109ea5a66744dd859bf16fe904b8d8b627adafb9408753161e766a92e7d681fa",
"sha256:6166864e23d6b5195a5cfed6cd9fed0fe774e226d8f854fcb23b7bbef0350233"
"sha256:69805d6b69f56eb05b62daea3a7dbd7aa44324ad1306445e05da8060232d00f4",
"sha256:a8774e55b59fd9fc893b0d05e9bfc6f47081f46ff5b46f39ccf24631b7be356b"
],
"markers": "python_version >= '3.6'",
"version": "==4.6"
"version": "==4.7"
},
"ruamel.yaml": {
"hashes": [
@ -970,10 +979,10 @@
},
"s3transfer": {
"hashes": [
"sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
"sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
"sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed",
"sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2"
],
"version": "==0.3.3"
"version": "==0.3.4"
},
"sentry-sdk": {
"hashes": [
@ -1007,11 +1016,11 @@
},
"structlog": {
"hashes": [
"sha256:7a48375db6274ed1d0ae6123c486472aa1d0890b08d314d2b016f3aa7f35990b",
"sha256:8a672be150547a93d90a7d74229a29e765be05bd156a35cdcc527ebf68e9af92"
"sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd",
"sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174"
],
"index": "pypi",
"version": "==20.1.0"
"version": "==20.2.0"
},
"swagger-spec-validator": {
"hashes": [
@ -1071,23 +1080,23 @@
"secure"
],
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
],
"index": "pypi",
"markers": null,
"version": "==1.26.2"
"version": "==1.26.3"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
"sha256:6707fa7f4dbd86fd6982a2d4ecdaad2704e4514d23a1e4278104311288b04691",
"sha256:d19ca083bebd212843e01f689900e5c637a292c63bb336c7f0735a99300a5f38"
"sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c",
"sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"
],
"index": "pypi",
"version": "==0.13.2"
"version": "==0.13.3"
},
"uvloop": {
"hashes": [
@ -1318,66 +1327,66 @@
},
"coverage": {
"hashes": [
"sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297",
"sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1",
"sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497",
"sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606",
"sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528",
"sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b",
"sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4",
"sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830",
"sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1",
"sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f",
"sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d",
"sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3",
"sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8",
"sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500",
"sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7",
"sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb",
"sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b",
"sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059",
"sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b",
"sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72",
"sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36",
"sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277",
"sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c",
"sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631",
"sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff",
"sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8",
"sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec",
"sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b",
"sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7",
"sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105",
"sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b",
"sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c",
"sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b",
"sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98",
"sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4",
"sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879",
"sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f",
"sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4",
"sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044",
"sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e",
"sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899",
"sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f",
"sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448",
"sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714",
"sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2",
"sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d",
"sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd",
"sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7",
"sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"
"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"
],
"index": "pypi",
"version": "==5.3.1"
"version": "==5.4"
},
"django": {
"hashes": [
"sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2",
"sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
],
"index": "pypi",
"version": "==3.1.4"
"version": "==3.1.5"
},
"django-debug-toolbar": {
"hashes": [
@ -1417,10 +1426,10 @@
},
"gitpython": {
"hashes": [
"sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b",
"sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"
"sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac",
"sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5"
],
"version": "==3.1.11"
"version": "==3.1.12"
},
"iniconfig": {
"hashes": [
@ -1591,11 +1600,11 @@
},
"pytest": {
"hashes": [
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
],
"index": "pypi",
"version": "==6.2.1"
"version": "==6.2.2"
},
"pytest-django": {
"hashes": [
@ -1607,29 +1616,37 @@
},
"pytz": {
"hashes": [
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
],
"version": "==2020.4"
"version": "==2020.5"
},
"pyyaml": {
"hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
],
"index": "pypi",
"version": "==5.3.1"
"version": "==5.4.1"
},
"regex": {
"hashes": [
@ -1706,17 +1723,17 @@
},
"smmap": {
"hashes": [
"sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
"sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
"sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714",
"sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50"
],
"version": "==3.0.4"
"version": "==3.0.5"
},
"snowballstemmer": {
"hashes": [
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
"sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
"sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
],
"version": "==2.0.0"
"version": "==2.1.0"
},
"sqlparse": {
"hashes": [
@ -1741,38 +1758,38 @@
},
"typed-ast": {
"hashes": [
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d",
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c",
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d",
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c",
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395",
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072",
"sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298",
"sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91",
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
"sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
"sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
"sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
"sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
"sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
"sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
"sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
"sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
"sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
"sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
"sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
"sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
"sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
"sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
"sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
"sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
"sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
"sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
"sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
"sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
"sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
],
"version": "==1.4.1"
"version": "==1.4.2"
},
"typing-extensions": {
"hashes": [
@ -1787,12 +1804,12 @@
"secure"
],
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
],
"index": "pypi",
"markers": null,
"version": "==1.26.2"
"version": "==1.26.3"
},
"wrapt": {
"hashes": [

View File

@ -2,13 +2,11 @@
## Supported Versions
As authentik is currently in a pre-stable, only the latest "stable" version is supported. After authentik 1.0, this will change.
| Version | Supported |
| -------- | ------------------ |
| 0.11.x | :white_check_mark: |
| 0.12.x | :white_check_mark: |
| 0.13.x | :white_check_mark: |
| Version | Supported |
| ---------- | ------------------ |
| 0.13.x | :white_check_mark: |
| 0.14.x | :white_check_mark: |
| 2021.1.x | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -1,2 +1,2 @@
"""authentik"""
__version__ = "0.14.0-rc1"
__version__ = "2021.1.4-stable"

View File

@ -14,7 +14,7 @@ from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.lib.tasks import TaskInfo
from authentik.events.monitored_tasks import TaskInfo
class TaskSerializer(Serializer):

View File

@ -2,11 +2,11 @@
from django.core.cache import cache
from packaging.version import parse
from requests import RequestException, get
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik import __version__
from authentik.events.models import Event, EventAction
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()

View File

@ -160,6 +160,17 @@
</ak-modal-button>
</li>
{% endfor %}
<li>
<ak-modal-button href="{% url 'authentik_admin:provider-saml-from-metadata' %}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{% trans 'SAML Provider from Metadata' %}<br>
<small>
{% trans "Create a SAML Provider by importing its Metadata." %}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
</ul>
</ak-dropdown>
</div>

View File

@ -38,7 +38,7 @@
{% for task in object_list %}
<tr role="row">
<th role="columnheader">
<pre>{{ task.task_name }}</pre>
<span>{{ task.html_name|join:"_&shy;" }}</span>
</th>
<td role="cell">
<span>

View File

@ -2,7 +2,7 @@
from django import template
from django.db.models import Model
from django.utils.html import mark_safe
from structlog import get_logger
from structlog.stdlib import get_logger
register = template.Library()
LOGGER = get_logger()

View File

@ -32,7 +32,7 @@ REQUEST_MOCK_VALID = Mock(
return_value=MockResponse(
200,
"""{
"tag_name": "version/1.2.3"
"tag_name": "version/99999999.9999999"
}""",
)
)
@ -47,10 +47,11 @@ class TestAdminTasks(TestCase):
def test_version_valid_response(self):
"""Test Update checker with valid response"""
update_latest_version.delay().get()
self.assertEqual(cache.get(VERSION_CACHE_KEY), "1.2.3")
self.assertEqual(cache.get(VERSION_CACHE_KEY), "99999999.9999999")
self.assertTrue(
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE, context__new_version="1.2.3"
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
).exists()
)
# test that a consecutive check doesn't create a duplicate event
@ -58,7 +59,8 @@ class TestAdminTasks(TestCase):
self.assertEqual(
len(
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE, context__new_version="1.2.3"
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
)
),
1,

View File

@ -4,6 +4,8 @@ from django.urls import path
from authentik.admin.views import (
applications,
certificate_key_pair,
events_notifications_rules,
events_notifications_transports,
flows,
groups,
outposts,
@ -352,4 +354,36 @@ urlpatterns = [
tasks.TaskListView.as_view(),
name="tasks",
),
# Event Notification Transpots
path(
"events/transports/create/",
events_notifications_transports.NotificationTransportCreateView.as_view(),
name="notification-transport-create",
),
path(
"events/transports/<uuid:pk>/update/",
events_notifications_transports.NotificationTransportUpdateView.as_view(),
name="notification-transport-update",
),
path(
"events/transports/<uuid:pk>/delete/",
events_notifications_transports.NotificationTransportDeleteView.as_view(),
name="notification-transport-delete",
),
# Event Notification Rules
path(
"events/rules/create/",
events_notifications_rules.NotificationRuleCreateView.as_view(),
name="notification-rule-create",
),
path(
"events/rules/<uuid:pk>/update/",
events_notifications_rules.NotificationRuleUpdateView.as_view(),
name="notification-rule-update",
),
path(
"events/rules/<uuid:pk>/delete/",
events_notifications_rules.NotificationRuleDeleteView.as_view(),
name="notification-rule-delete",
),
]

View File

@ -29,7 +29,7 @@ class ApplicationCreateView(
permission_required = "authentik_core.add_application"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:applications")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Application")
@ -47,7 +47,7 @@ class ApplicationUpdateView(
permission_required = "authentik_core.change_application"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:applications")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Application")
@ -60,5 +60,5 @@ class ApplicationDeleteView(
permission_required = "authentik_core.delete_application"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:applications")
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Application")

View File

@ -0,0 +1,64 @@
"""authentik NotificationRule administration"""
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.urls import reverse_lazy
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.events.forms import NotificationRuleForm
from authentik.events.models import NotificationRule
from authentik.lib.views import CreateAssignPermView
class NotificationRuleCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new NotificationRule"""
model = NotificationRule
form_class = NotificationRuleForm
permission_required = "authentik_events.add_NotificationRule"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Notification Rule")
class NotificationRuleUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
):
"""Update application"""
model = NotificationRule
form_class = NotificationRuleForm
permission_required = "authentik_events.change_NotificationRule"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Notification Rule")
class NotificationRuleDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView
):
"""Delete application"""
model = NotificationRule
permission_required = "authentik_events.delete_NotificationRule"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Notification Rule")

View File

@ -0,0 +1,64 @@
"""authentik NotificationTransport administration"""
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.urls import reverse_lazy
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.events.forms import NotificationTransportForm
from authentik.events.models import NotificationTransport
from authentik.lib.views import CreateAssignPermView
class NotificationTransportCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new NotificationTransport"""
model = NotificationTransport
form_class = NotificationTransportForm
permission_required = "authentik_events.add_notificationtransport"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully created Notification Transport")
class NotificationTransportUpdateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
):
"""Update application"""
model = NotificationTransport
form_class = NotificationTransportForm
permission_required = "authentik_events.change_notificationtransport"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully updated Notification Transport")
class NotificationTransportDeleteView(
LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView
):
"""Delete application"""
model = NotificationTransport
permission_required = "authentik_events.delete_notificationtransport"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_core:shell")
success_message = _("Successfully deleted Notification Transport")

View File

@ -17,6 +17,7 @@ from authentik.admin.views.utils import (
SearchListMixin,
UserPaginateListMixin,
)
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.forms import FlowForm, FlowImportForm
from authentik.flows.models import Flow
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
@ -25,7 +26,7 @@ from authentik.flows.transfer.exporter import FlowExporter
from authentik.flows.transfer.importer import FlowImporter
from authentik.flows.views import SESSION_KEY_PLAN, FlowPlanner
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import CreateAssignPermView
from authentik.lib.views import CreateAssignPermView, bad_request_message
class FlowListView(
@ -103,8 +104,17 @@ class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi
flow: Flow = self.get_object()
planner = FlowPlanner(flow)
planner.use_cache = False
plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user})
self.request.session[SESSION_KEY_PLAN] = plan
try:
plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user})
self.request.session[SESSION_KEY_PLAN] = plan
except FlowNonApplicableException as exc:
return bad_request_message(
request,
_(
"Flow not applicable to current user/request: %(messages)s"
% {"messages": str(exc)}
),
)
return redirect_with_qs(
"authentik_flows:flow-executor-shell",
self.request.GET,

View File

@ -5,10 +5,11 @@ from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.utils.translation import gettext as _
from django.views.generic import FormView
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm
from authentik.admin.mixins import AdminRequiredMixin
from authentik.core.api.applications import user_app_cache_key
LOGGER = get_logger()
@ -26,6 +27,9 @@ class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
keys = cache.keys("policy_*")
cache.delete_many(keys)
LOGGER.debug("Cleared Policy cache", keys=len(keys))
# Also delete user application cache
keys = user_app_cache_key("*")
cache.delete_many(keys)
return super().post(request, *args, **kwargs)

View File

@ -4,7 +4,7 @@ from typing import Any, Dict
from django.views.generic.base import TemplateView
from authentik.admin.mixins import AdminRequiredMixin
from authentik.lib.tasks import TaskInfo, TaskResultStatus
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
class TaskListView(AdminRequiredMixin, TemplateView):

View File

@ -5,7 +5,7 @@ from typing import Any, Optional, Tuple, Union
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.request import Request
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import Token, TokenIntents, User

View File

@ -19,7 +19,10 @@ from authentik.core.api.sources import SourceViewSet
from authentik.core.api.tokens import TokenViewSet
from authentik.core.api.users import UserViewSet
from authentik.crypto.api import CertificateKeyPairViewSet
from authentik.events.api import EventViewSet
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,
@ -37,6 +40,7 @@ from authentik.policies.api import (
PolicyViewSet,
)
from authentik.policies.dummy.api import DummyPolicyViewSet
from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet
from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet
from authentik.policies.expression.api import ExpressionPolicyViewSet
from authentik.policies.group_membership.api import GroupMembershipPolicyViewSet
@ -97,6 +101,9 @@ router.register("flows/bindings", FlowStageBindingViewSet)
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
router.register("events/events", EventViewSet)
router.register("events/notifications", NotificationViewSet)
router.register("events/transports", NotificationTransportViewSet)
router.register("events/rules", NotificationRuleViewSet)
router.register("sources/all", SourceViewSet)
router.register("sources/ldap", LDAPSourceViewSet)
@ -107,6 +114,7 @@ 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)
router.register("policies/group_membership", GroupMembershipPolicyViewSet)
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
router.register("policies/password_expiry", PasswordExpiryPolicyViewSet)

View File

@ -4,7 +4,7 @@ from django.apps import AppConfig, apps
from django.contrib import admin
from django.contrib.admin.sites import AlreadyRegistered
from guardian.admin import GuardedModelAdmin
from structlog import get_logger
from structlog.stdlib import get_logger
LOGGER = get_logger()

View File

@ -1,4 +1,5 @@
"""Application API Views"""
from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import Http404
from guardian.shortcuts import get_objects_for_user
@ -10,6 +11,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger
from authentik.admin.api.metrics import get_events_per_1h
from authentik.core.api.providers import ProviderSerializer
@ -17,6 +19,13 @@ from authentik.core.models import Application
from authentik.events.models import EventAction
from authentik.policies.engine import PolicyEngine
LOGGER = get_logger()
def user_app_cache_key(user_pk: str) -> str:
"""Cache key where application list for user is saved"""
return f"user_app_cache_{user_pk}"
class ApplicationSerializer(ModelSerializer):
"""Application Serializer"""
@ -68,16 +77,35 @@ class ApplicationViewSet(ModelViewSet):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def list(self, request: Request) -> Response:
"""Custom list method that checks Policy based access instead of guardian"""
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
allowed_applications = []
def _get_allowed_applications(self, queryset: QuerySet) -> list[Application]:
applications = []
for application in queryset:
engine = PolicyEngine(application, self.request.user, self.request)
engine.build()
if engine.passing:
allowed_applications.append(application)
applications.append(application)
return applications
def list(self, request: Request) -> Response:
"""Custom list method that checks Policy based access instead of guardian"""
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
should_cache = "search" not in request.GET
allowed_applications = []
if not should_cache:
allowed_applications = self._get_allowed_applications(queryset)
if should_cache:
LOGGER.debug("Caching allowed application list")
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
if not allowed_applications:
allowed_applications = self._get_allowed_applications(queryset)
cache.set(
user_app_cache_key(self.request.user.pk),
allowed_applications,
timeout=86400,
)
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)

View File

@ -23,7 +23,7 @@ class PropertyMappingSerializer(ModelSerializer):
class PropertyMappingViewSet(ReadOnlyModelViewSet):
"""PropertyMapping Viewset"""
queryset = PropertyMapping.objects.all()
queryset = PropertyMapping.objects.none()
serializer_class = PropertyMappingSerializer
def get_queryset(self):

View File

@ -39,7 +39,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
class ProviderViewSet(ModelViewSet):
"""Provider Viewset"""
queryset = Provider.objects.all()
queryset = Provider.objects.none()
serializer_class = ProviderSerializer
filterset_fields = {
"application": ["isnull"],

View File

@ -31,7 +31,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
class SourceViewSet(ReadOnlyModelViewSet):
"""Source Viewset"""
queryset = Source.objects.all()
queryset = Source.objects.none()
serializer_class = SourceSerializer
lookup_field = "slug"

View File

@ -34,7 +34,7 @@ class UserSerializer(ModelSerializer):
class UserViewSet(ModelViewSet):
"""User Viewset"""
queryset = User.objects.all()
queryset = User.objects.none()
serializer_class = UserSerializer
def get_queryset(self):

View File

@ -1,7 +1,7 @@
"""Channels base classes"""
from channels.exceptions import DenyConnection
from channels.generic.websocket import JsonWebsocketConsumer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.api.auth import token_from_header
from authentik.core.models import User

View File

@ -28,7 +28,7 @@ class PropertyMappingEvaluator(BaseEvaluator):
event = Event.new(
EventAction.PROPERTY_MAPPING_EXCEPTION,
expression=expression_source,
error=error_string,
message=error_string,
)
if "user" in self._context:
event.set_user(self._context["user"])

View File

@ -15,7 +15,7 @@ from django.utils.translation import gettext_lazy as _
from guardian.mixins import GuardianUserMixin
from model_utils.managers import InheritanceManager
from rest_framework.serializers import Serializer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.signals import password_changed

View File

@ -8,10 +8,10 @@ from dbbackup.db.exceptions import CommandConnectorError
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.core import management
from django.utils.timezone import now
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import ExpiringModel
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()

View File

@ -9,14 +9,14 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}">
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-addons.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/fontawesome.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<script src="{% url 'javascript-catalog' %}"></script>
<script src="{% static 'dist/main.js' %}" type="module"></script>
<link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-addons.css' %}?v={{ ak_version }}">
<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,5 +1,6 @@
{% load i18n %}
{% load authentik_user_settings %}
{% load authentik_utils %}
<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1">
@ -12,47 +13,45 @@
<p>{% trans "Configure settings relevant to your user profile." %}</p>
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{% url 'authentik_core:user-details' %}">
<div slot="body"></div>
</ak-site-shell>
<ak-tabs>
<section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{% url 'authentik_core:user-details' %}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{% url 'authentik_core:user-tokens' %}">
<div slot="body"></div>
</ak-site-shell>
</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>
</section>
{% user_stages as user_stages_loc %}
{% for stage, stage_link in user_stages_loc.items %}
<section slot="page-{{ stage.pk }}" data-tab-title="{{ stage|verbose_name }}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{{ stage_link }}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</div>
</section>
{% user_stages as user_stages_loc %}
{% for stage in user_stages_loc %}
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{{ stage }}">
<div slot="body"></div>
</ak-site-shell>
</section>
{% endfor %}
{% user_sources as user_sources_loc %}
{% for source, source_link in user_sources_loc.item %}
<section slot="page-{{ source.pk }}" data-tab-title="{{ source|verbose_name }}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{{ source_link }}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</div>
</section>
{% endfor %}
{% user_sources as user_sources_loc %}
{% for source in user_sources_loc %}
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{{ source }}">
<div slot="body"></div>
</ak-site-shell>
</div>
</div>
</section>
{% endfor %}
</section>
{% endfor %}
</ak-tabs>
</main>
</div>

View File

@ -13,26 +13,26 @@ register = template.Library()
@register.simple_tag(takes_context=True)
# pylint: disable=unused-argument
def user_stages(context: RequestContext) -> list[str]:
def user_stages(context: RequestContext) -> dict[Stage, str]:
"""Return list of all stages which apply to user"""
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
matching_stages: list[str] = []
matching_stages: dict[Stage, str] = {}
for stage in _all_stages:
user_settings = stage.ui_user_settings
if not user_settings:
continue
matching_stages.append(user_settings)
matching_stages[stage] = user_settings
return matching_stages
@register.simple_tag(takes_context=True)
def user_sources(context: RequestContext) -> list[str]:
def user_sources(context: RequestContext) -> dict[Source, str]:
"""Return a list of all sources which are enabled for the user"""
user = context.get("request").user
_all_sources: Iterable[Source] = Source.objects.filter(
enabled=True
).select_subclasses()
matching_sources: list[str] = []
matching_sources: dict[Source, str] = {}
for source in _all_sources:
user_settings = source.ui_user_settings
if not user_settings:
@ -40,5 +40,5 @@ def user_sources(context: RequestContext) -> list[str]:
policy_engine = PolicyEngine(source, user, context.get("request"))
policy_engine.build()
if policy_engine.passing:
matching_sources.append(user_settings)
matching_sources[source] = user_settings
return matching_sources

View File

@ -25,7 +25,7 @@ urlpatterns = [
name="user-tokens-delete",
),
# Libray
path("library/", library.LibraryView.as_view(), name="overview"),
path("library", library.LibraryView.as_view(), name="overview"),
# Impersonation
path(
"-/impersonation/<int:user_id>/",

View File

@ -3,7 +3,7 @@
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.views import View
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER,

View File

View File

@ -48,6 +48,15 @@ class EventViewSet(ReadOnlyModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
ordering = ["-created"]
search_fields = [
"user",
"action",
"app",
"context",
"client_ip",
]
filterset_fields = ["action"]
@swagger_auto_schema(
method="GET", responses={200: EventTopPerUserSerialier(many=True)}

View File

@ -0,0 +1,53 @@
"""Notification API Views"""
from rest_framework import mixins
from rest_framework.fields import ReadOnlyField
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from authentik.events.api.event import EventSerializer
from authentik.events.models import Notification
class NotificationSerializer(ModelSerializer):
"""Notification Serializer"""
body = ReadOnlyField()
severity = ReadOnlyField()
event = EventSerializer()
class Meta:
model = Notification
fields = [
"pk",
"severity",
"body",
"created",
"event",
"seen",
]
class NotificationViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Notification Viewset"""
queryset = Notification.objects.all()
serializer_class = NotificationSerializer
filterset_fields = [
"severity",
"body",
"created",
"event",
"seen",
]
def get_queryset(self):
if not self.request:
return super().get_queryset()
return Notification.objects.filter(user=self.request.user)

View File

@ -0,0 +1,28 @@
"""NotificationRule API Views"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.events.models import NotificationRule
class NotificationRuleSerializer(ModelSerializer):
"""NotificationRule Serializer"""
class Meta:
model = NotificationRule
depth = 2
fields = [
"pk",
"name",
"transports",
"severity",
"group",
]
class NotificationRuleViewSet(ModelViewSet):
"""NotificationRule Viewset"""
queryset = NotificationRule.objects.all()
serializer_class = NotificationRuleSerializer

View File

@ -0,0 +1,66 @@
"""NotificationTransport API Views"""
from django.http.response import Http404
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.events.models import (
Notification,
NotificationSeverity,
NotificationTransport,
NotificationTransportError,
TransportMode,
)
class NotificationTransportSerializer(ModelSerializer):
"""NotificationTransport Serializer"""
mode_verbose = SerializerMethodField()
def get_mode_verbose(self, instance: NotificationTransport):
"""Return selected mode with a UI Label"""
return TransportMode(instance.mode).label
class Meta:
model = NotificationTransport
fields = [
"pk",
"name",
"mode",
"mode_verbose",
"webhook_url",
]
class NotificationTransportViewSet(ModelViewSet):
"""NotificationTransport Viewset"""
queryset = NotificationTransport.objects.all()
serializer_class = NotificationTransportSerializer
@action(detail=True, methods=["post"])
# pylint: disable=invalid-name
def test(self, request: Request, pk=None) -> Response:
"""Send example notification using selected transport. Requires
Modify permissions."""
transports = get_objects_for_user(
request.user, "authentik_events.change_notificationtransport"
).filter(pk=pk)
if not transports.exists():
raise Http404
transport: NotificationTransport = transports.first()
notification = Notification(
severity=NotificationSeverity.NOTICE,
body=f"Test Notification from transport {transport.name}",
user=request.user,
)
try:
return Response(transport.send(notification))
except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=503)

View File

@ -10,7 +10,6 @@ class AuthentikEventsConfig(AppConfig):
name = "authentik.events"
label = "authentik_events"
verbose_name = "authentik Events"
mountpoint = "events/"
def ready(self):
import_module("authentik.events.signals")

47
authentik/events/forms.py Normal file
View File

@ -0,0 +1,47 @@
"""authentik events NotificationTransport forms"""
from django import forms
from django.utils.translation import gettext_lazy as _
from authentik.events.models import NotificationRule, NotificationTransport
class NotificationTransportForm(forms.ModelForm):
"""NotificationTransport Form"""
class Meta:
model = NotificationTransport
fields = [
"name",
"mode",
"webhook_url",
]
widgets = {
"name": forms.TextInput(),
"webhook_url": forms.TextInput(),
}
labels = {
"webhook_url": _("Webhook URL"),
}
help_texts = {
"webhook_url": _(
("Only required when the Generic or Slack Webhook is used.")
),
}
class NotificationRuleForm(forms.ModelForm):
"""NotificationRule Form"""
class Meta:
model = NotificationRule
fields = [
"name",
"group",
"transports",
"severity",
]
widgets = {
"name": forms.TextInput(),
}

View File

@ -6,9 +6,10 @@ from django.contrib.auth.models import User
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
from django.http import HttpRequest, HttpResponse
from guardian.models import UserObjectPermission
from authentik.core.middleware import LOCAL
from authentik.events.models import Event, EventAction
from authentik.events.models import Event, EventAction, Notification
from authentik.events.signals import EventNewThread
from authentik.events.utils import model_to_dict
@ -63,7 +64,7 @@ class AuditMiddleware:
user: User, request: HttpRequest, sender, instance: Model, created: bool, **_
):
"""Signal handler for all object's post_save"""
if isinstance(instance, Event):
if isinstance(instance, (Event, Notification, UserObjectPermission)):
return
action = EventAction.MODEL_CREATED if created else EventAction.MODEL_UPDATED
@ -75,7 +76,7 @@ class AuditMiddleware:
user: User, request: HttpRequest, sender, instance: Model, **_
):
"""Signal handler for all object's pre_delete"""
if isinstance(instance, Event):
if isinstance(instance, (Event, Notification, UserObjectPermission)):
return
EventNewThread(

View File

@ -0,0 +1,148 @@
# Generated by Django 3.1.4 on 2021-01-11 16:36
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("authentik_policies", "0004_policy_execution_logging"),
("authentik_core", "0016_auto_20201202_2234"),
("authentik_events", "0009_auto_20201227_1210"),
]
operations = [
migrations.CreateModel(
name="NotificationTransport",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField(unique=True)),
(
"mode",
models.TextField(
choices=[
("webhook", "Generic Webhook"),
("webhook_slack", "Slack Webhook (Slack/Discord)"),
("email", "Email"),
]
),
),
("webhook_url", models.TextField(blank=True)),
],
options={
"verbose_name": "Notification Transport",
"verbose_name_plural": "Notification Transports",
},
),
migrations.CreateModel(
name="NotificationRule",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policybindingmodel",
),
),
("name", models.TextField(unique=True)),
(
"severity",
models.TextField(
choices=[
("notice", "Notice"),
("warning", "Warning"),
("alert", "Alert"),
],
default="notice",
help_text="Controls which severity level the created notifications will have.",
),
),
(
"group",
models.ForeignKey(
blank=True,
help_text="Define which group of users this notification should be sent and shown to. If left empty, Notification won't ben sent.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_core.group",
),
),
(
"transports",
models.ManyToManyField(
help_text="Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.",
to="authentik_events.NotificationTransport",
),
),
],
options={
"verbose_name": "Notification Rule",
"verbose_name_plural": "Notification Rules",
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.CreateModel(
name="Notification",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"severity",
models.TextField(
choices=[
("notice", "Notice"),
("warning", "Warning"),
("alert", "Alert"),
]
),
),
("body", models.TextField()),
("created", models.DateTimeField(auto_now_add=True)),
("seen", models.BooleanField(default=False)),
(
"event",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_events.event",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Notification",
"verbose_name_plural": "Notifications",
},
),
]

View File

@ -0,0 +1,165 @@
# Generated by Django 3.1.4 on 2021-01-10 18:57
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.events.models import EventAction, NotificationSeverity, TransportMode
def notify_configuration_error(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model(
"authentik_policies_event_matcher", "EventMatcherPolicy"
)
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias)
.filter(name="authentik Admins", is_superuser=True)
.first()
)
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-configuration-error",
defaults={"action": EventAction.CONFIGURATION_ERROR},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-configuration-error",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(
name="default-email-transport"
)
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy,
defaults={
"order": 0,
},
)
def notify_update(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model(
"authentik_policies_event_matcher", "EventMatcherPolicy"
)
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias)
.filter(name="authentik Admins", is_superuser=True)
.first()
)
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-update",
defaults={"action": EventAction.UPDATE_AVAILABLE},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-update",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(
name="default-email-transport"
)
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy,
defaults={
"order": 0,
},
)
def notify_exception(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model(
"authentik_policies_event_matcher", "EventMatcherPolicy"
)
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias)
.filter(name="authentik Admins", is_superuser=True)
.first()
)
policy_policy_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-policy-exception",
defaults={"action": EventAction.POLICY_EXCEPTION},
)
policy_pm_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-property-mapping-exception",
defaults={"action": EventAction.PROPERTY_MAPPING_EXCEPTION},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-exception",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(
name="default-email-transport"
)
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy_policy_exc,
defaults={
"order": 0,
},
)
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy_pm_exc,
defaults={
"order": 1,
},
)
def transport_email_global(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
NotificationTransport.objects.using(db_alias).update_or_create(
name="default-email-transport",
defaults={"mode": TransportMode.EMAIL},
)
class Migration(migrations.Migration):
dependencies = [
(
"authentik_events",
"0010_notification_notificationtransport_notificationrule",
),
("authentik_core", "0016_auto_20201202_2234"),
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
("authentik_policies", "0004_policy_execution_logging"),
]
operations = [
migrations.RunPython(transport_email_global),
migrations.RunPython(notify_configuration_error),
migrations.RunPython(notify_update),
migrations.RunPython(notify_exception),
]

View File

@ -1,6 +1,6 @@
"""authentik events models"""
from inspect import getmodule, stack
from smtplib import SMTPException
from typing import Optional, Union
from uuid import uuid4
@ -9,19 +9,28 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext as _
from structlog import get_logger
from requests import RequestException, post
from structlog.stdlib import get_logger
from authentik import __version__
from authentik.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER,
SESSION_IMPERSONATE_USER,
)
from authentik.core.models import User
from authentik.core.models import Group, User
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
from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger("authentik.events")
class NotificationTransportError(SentryIgnoredException):
"""Error raised when a notification fails to be delivered"""
class EventAction(models.TextChoices):
"""All possible actions to save into the events log"""
@ -47,6 +56,9 @@ class EventAction(models.TextChoices):
POLICY_EXCEPTION = "policy_exception"
PROPERTY_MAPPING_EXCEPTION = "property_mapping_exception"
SYSTEM_TASK_EXECUTION = "system_task_execution"
SYSTEM_TASK_EXCEPTION = "system_task_exception"
CONFIGURATION_ERROR = "configuration_error"
MODEL_CREATED = "model_created"
@ -104,10 +116,12 @@ class Event(models.Model):
Events independently from requests.
`user` arguments optionally overrides user from requests."""
if hasattr(request, "user"):
self.user = get_user(
request.user,
request.session.get(SESSION_IMPERSONATE_ORIGINAL_USER, None),
)
original_user = None
if hasattr(request, "session"):
original_user = request.session.get(
SESSION_IMPERSONATE_ORIGINAL_USER, None
)
self.user = get_user(request.user, original_user)
if user:
self.user = get_user(user)
# Check if we're currently impersonating, and add that user
@ -127,9 +141,7 @@ class Event(models.Model):
def save(self, *args, **kwargs):
if not self._state.adding:
raise ValidationError(
"you may not edit an existing %s" % self._meta.model_name
)
raise ValidationError("you may not edit an existing Event")
LOGGER.debug(
"Created Event",
action=self.action,
@ -139,7 +151,219 @@ class Event(models.Model):
)
return super().save(*args, **kwargs)
@property
def summary(self) -> str:
"""Return a summary of this event."""
if "message" in self.context:
return self.context["message"]
return f"{self.action}: {self.context}"
def __str__(self) -> str:
return f"<Event action={self.action} user={self.user} context={self.context}>"
class Meta:
verbose_name = _("Event")
verbose_name_plural = _("Events")
class TransportMode(models.TextChoices):
"""Modes that a notification transport can send a notification"""
WEBHOOK = "webhook", _("Generic Webhook")
WEBHOOK_SLACK = "webhook_slack", _("Slack Webhook (Slack/Discord)")
EMAIL = "email", _("Email")
class NotificationTransport(models.Model):
"""Action which is executed when a Rule matches"""
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(unique=True)
mode = models.TextField(choices=TransportMode.choices)
webhook_url = models.TextField(blank=True)
def send(self, notification: "Notification") -> list[str]:
"""Send notification to user, called from async task"""
if self.mode == TransportMode.WEBHOOK:
return self.send_webhook(notification)
if self.mode == TransportMode.WEBHOOK_SLACK:
return self.send_webhook_slack(notification)
if self.mode == TransportMode.EMAIL:
return self.send_email(notification)
raise ValueError(f"Invalid mode {self.mode} set")
def send_webhook(self, notification: "Notification") -> list[str]:
"""Send notification to generic webhook"""
try:
response = post(
self.webhook_url,
json={
"body": notification.body,
"severity": notification.severity,
"user_email": notification.user.email,
"user_username": notification.user.username,
},
)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(exc.response.text) from exc
return [
response.status_code,
response.text,
]
def send_webhook_slack(self, notification: "Notification") -> list[str]:
"""Send notification to slack or slack-compatible endpoints"""
fields = [
{
"title": _("Severity"),
"value": notification.severity,
"short": True,
},
{
"title": _("Dispatched for user"),
"value": str(notification.user),
"short": True,
},
]
if notification.event:
for key, value in notification.event.context.items():
if not isinstance(value, str):
continue
# https://birdie0.github.io/discord-webhooks-guide/other/field_limits.html
if len(fields) >= 25:
continue
fields.append({"title": key[:256], "value": value[:1024]})
body = {
"username": "authentik",
"icon_url": "https://goauthentik.io/img/icon.png",
"attachments": [
{
"author_name": "authentik",
"author_link": "https://goauthentik.io",
"author_icon": "https://goauthentik.io/img/icon.png",
"title": notification.body,
"color": "#fd4b2d",
"fields": fields,
"footer": f"authentik v{__version__}",
}
],
}
if notification.event:
body["attachments"][0]["title"] = notification.event.action
body["attachments"][0]["text"] = notification.event.action
try:
response = post(self.webhook_url, json=body)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(exc.response.text) from exc
return [
response.status_code,
response.text,
]
def send_email(self, notification: "Notification") -> list[str]:
"""Send notification via global email configuration"""
body_trunc = (
(notification.body[:75] + "..")
if len(notification.body) > 75
else notification.body
)
mail = TemplateEmailMessage(
subject=f"authentik Notification: {body_trunc}",
template_name="email/generic.html",
to=[notification.user.email],
template_context={
"body": notification.body,
},
)
# Email is sent directly here, as the call to send() should have been from a task.
try:
from authentik.stages.email.tasks import send_mail
# pyright: reportGeneralTypeIssues=false
return send_mail(mail.__dict__) # pylint: disable=no-value-for-parameter
except (SMTPException, ConnectionError, OSError) as exc:
raise NotificationTransportError from exc
def __str__(self) -> str:
return f"Notification Transport {self.name}"
class Meta:
verbose_name = _("Notification Transport")
verbose_name_plural = _("Notification Transports")
class NotificationSeverity(models.TextChoices):
"""Severity images that a notification can have"""
NOTICE = "notice", _("Notice")
WARNING = "warning", _("Warning")
ALERT = "alert", _("Alert")
class Notification(models.Model):
"""Event Notification"""
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
severity = models.TextField(choices=NotificationSeverity.choices)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
event = models.ForeignKey(Event, on_delete=models.SET_NULL, null=True, blank=True)
seen = models.BooleanField(default=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self) -> str:
body_trunc = (self.body[:75] + "..") if len(self.body) > 75 else self.body
return f"Notification for user {self.user}: {body_trunc}"
class Meta:
verbose_name = _("Notification")
verbose_name_plural = _("Notifications")
class NotificationRule(PolicyBindingModel):
"""Decide when to create a Notification based on policies attached to this object."""
name = models.TextField(unique=True)
transports = models.ManyToManyField(
NotificationTransport,
help_text=_(
(
"Select which transports should be used to notify the user. If none are "
"selected, the notification will only be shown in the authentik UI."
)
),
)
severity = models.TextField(
choices=NotificationSeverity.choices,
default=NotificationSeverity.NOTICE,
help_text=_(
"Controls which severity level the created notifications will have."
),
)
group = models.ForeignKey(
Group,
help_text=_(
(
"Define which group of users this notification should be sent and shown to. "
"If left empty, Notification won't ben sent."
)
),
null=True,
blank=True,
on_delete=models.SET_NULL,
)
def __str__(self) -> str:
return f"Notification Rule {self.name}"
class Meta:
verbose_name = _("Notification Rule")
verbose_name_plural = _("Notification Rules")

View File

@ -8,6 +8,8 @@ from typing import Any, Dict, List, Optional
from celery import Task
from django.core.cache import cache
from authentik.events.models import Event, EventAction
class TaskResultStatus(Enum):
"""Possible states of tasks"""
@ -52,6 +54,11 @@ class TaskInfo:
task_description: Optional[str] = field(default=None)
@property
def html_name(self) -> list[str]:
"""Get task_name, but split on underscores, so we can join in the html template."""
return self.task_name.split("_")
@staticmethod
def all() -> Dict[str, "TaskInfo"]:
"""Get all TaskInfo objects"""
@ -117,6 +124,13 @@ class MonitoredTask(Task):
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
Event.new(
EventAction.SYSTEM_TASK_EXECUTION,
message=(
f"Task {self.__name__} finished successfully: "
"\n".join(self._result.messages)
),
).save()
return super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
# pylint: disable=too-many-arguments
@ -133,6 +147,13 @@ class MonitoredTask(Task):
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
Event.new(
EventAction.SYSTEM_TASK_EXCEPTION,
message=(
f"Task {self.__name__} encountered an error: "
"\n".join(self._result.messages)
),
).save()
return super().on_failure(exc, task_id, args, kwargs, einfo=einfo)
def run(self, *args, **kwargs):

View File

@ -7,12 +7,16 @@ from django.contrib.auth.signals import (
user_logged_out,
user_login_failed,
)
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.http import HttpRequest
from authentik.core.models import User
from authentik.core.signals import password_changed
from authentik.events.models import Event, EventAction
from authentik.events.tasks import event_notification_handler
from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.user_write.signals import user_write
@ -44,6 +48,11 @@ class EventNewThread(Thread):
def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
"""Log successful login"""
thread = EventNewThread(EventAction.LOGIN, request)
if SESSION_KEY_PLAN in request.session:
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
if PLAN_CONTEXT_SOURCE in flow_plan.context:
# Login request came from an external source, save it in the context
thread.kwargs["using_source"] = flow_plan.context[PLAN_CONTEXT_SOURCE]
thread.user = user
thread.run()
@ -95,3 +104,10 @@ def on_password_changed(sender, user: User, password: str, **_):
"""Log password change"""
thread = EventNewThread(EventAction.PASSWORD_SET, None, user=user)
thread.run()
@receiver(post_save, sender=Event)
# pylint: disable=unused-argument
def event_post_save_notification(sender, instance: Event, **_):
"""Start task to check if any policies trigger an notification on this event"""
event_notification_handler.delay(instance.event_uuid.hex)

99
authentik/events/tasks.py Normal file
View File

@ -0,0 +1,99 @@
"""Event notification tasks"""
from guardian.shortcuts import get_anonymous_user
from structlog import get_logger
from authentik.events.models import (
Event,
Notification,
NotificationRule,
NotificationTransport,
NotificationTransportError,
)
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.policies.engine import PolicyEngine, PolicyEngineMode
from authentik.policies.models import PolicyBinding
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task()
def event_notification_handler(event_uuid: str):
"""Start task for each trigger definition"""
for trigger in NotificationRule.objects.all():
event_trigger_handler.apply_async(
args=[event_uuid, trigger.name], queue="authentik_events"
)
@CELERY_APP.task()
def event_trigger_handler(event_uuid: str, trigger_name: str):
"""Check if policies attached to NotificationRule match event"""
event: Event = Event.objects.get(event_uuid=event_uuid)
trigger: NotificationRule = NotificationRule.objects.get(name=trigger_name)
if "policy_uuid" in event.context:
policy_uuid = event.context["policy_uuid"]
if PolicyBinding.objects.filter(
target__in=NotificationRule.objects.all().values_list(
"pbm_uuid", flat=True
),
policy=policy_uuid,
).exists():
# If policy that caused this event to be created is attached
# to *any* NotificationRule, we return early.
# This is the most effective way to prevent infinite loops.
LOGGER.debug(
"e(trigger): attempting to prevent infinite loop", trigger=trigger
)
return
if not trigger.group:
LOGGER.debug("e(trigger): trigger has no group", trigger=trigger)
return
LOGGER.debug("e(trigger): checking if trigger applies", trigger=trigger)
policy_engine = PolicyEngine(trigger, get_anonymous_user())
policy_engine.mode = PolicyEngineMode.MODE_OR
policy_engine.empty_result = False
policy_engine.use_cache = False
policy_engine.request.context["event"] = event
policy_engine.build()
result = policy_engine.result
if not result.passing:
return
LOGGER.debug("e(trigger): event trigger matched", trigger=trigger)
# Create the notification objects
for user in trigger.group.users.all():
notification = Notification.objects.create(
severity=trigger.severity, body=event.summary, event=event, user=user
)
for transport in trigger.transports.all():
notification_transport.apply_async(
args=[notification.pk, transport.pk], queue="authentik_events"
)
@CELERY_APP.task(
bind=True,
autoretry_for=(NotificationTransportError,),
retry_backoff=True,
base=MonitoredTask,
)
def notification_transport(
self: MonitoredTask, notification_pk: int, transport_pk: int
):
"""Send notification over specified transport"""
self.save_on_success = False
try:
notification: Notification = Notification.objects.get(pk=notification_pk)
transport: NotificationTransport = NotificationTransport.objects.get(
pk=transport_pk
)
transport.send(notification)
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
except NotificationTransportError as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
raise exc

View File

@ -1,90 +0,0 @@
{% extends "base/page.html" %}
{% load i18n %}
{% load authentik_utils %}
{% block page_content %}
<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-catalog"></i>
{% trans 'Event Log' %}
</h1>
</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">
{% include 'partials/toolbar_search.html' %}
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
{% 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 'Action' %}</th>
<th role="columnheader" scope="col">{% trans 'Context' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Creation Date' %}</th>
<th role="columnheader" scope="col">{% trans 'Client IP' %}</th>
</tr>
</thead>
<tbody role="rowgroup">
{% for entry in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ entry.action }}</div>
<small>{{ entry.app|default:'-' }}</small>
</div>
</th>
<td role="cell">
<div>
<div>
<code>{{ entry.context }}</code>
</div>
{% if entry.user.on_behalf_of %}
<small>
{% blocktrans with username=entry.user.on_behalf_of.username %}
On behalf of {{ username }}
{% endblocktrans %}
</small>
{% endif %}
</div>
</td>
<td role="cell">
<div>
<div>{{ entry.user.username }}</div>
<small>
{% blocktrans with pk=entry.user.pk %}
ID: {{ pk }}
{% endblocktrans %}
</small>
</div>
</td>
<td role="cell">
<span>
{{ entry.created }}
</span>
</td>
<td role="cell">
<span>
{{ entry.client_ip }}
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-pagination pf-m-bottom">
{% include 'partials/pagination.html' %}
</div>
</div>
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,24 @@
"""Event API tests"""
from django.shortcuts import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.events.models import Event, EventAction
class TestEventsAPI(APITestCase):
"""Test Event API"""
def test_top_n(self):
"""Test top_per_user"""
user = User.objects.get(username="akadmin")
self.client.force_login(user)
event = Event.new(EventAction.AUTHORIZE_APPLICATION)
event.save() # We save to ensure nothing is un-saveable
response = self.client.get(
reverse("authentik_api:event-top-per-user"),
data={"filter_action": EventAction.AUTHORIZE_APPLICATION},
)
self.assertEqual(response.status_code, 200)

View File

@ -1,9 +1,10 @@
"""events event tests"""
"""event tests"""
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Group
from authentik.events.models import Event
from authentik.policies.dummy.models import DummyPolicy
@ -13,14 +14,24 @@ class TestEvents(TestCase):
def test_new_with_model(self):
"""Create a new Event passing a model as kwarg"""
event = Event.new("unittest", test={"model": get_anonymous_user()})
test_model = Group.objects.create(name="test")
event = Event.new("unittest", test={"model": test_model})
event.save() # We save to ensure nothing is un-saveable
model_content_type = ContentType.objects.get_for_model(get_anonymous_user())
model_content_type = ContentType.objects.get_for_model(test_model)
self.assertEqual(
event.context.get("test").get("model").get("app"),
model_content_type.app_label,
)
def test_new_with_user(self):
"""Create a new Event passing a user as kwarg"""
event = Event.new("unittest", test={"model": get_anonymous_user()})
event.save() # We save to ensure nothing is un-saveable
self.assertEqual(
event.context.get("test").get("model").get("username"),
get_anonymous_user().username,
)
def test_new_with_uuid_model(self):
"""Create a new Event passing a model (with UUID PK) as kwarg"""
temp_model = DummyPolicy.objects.create(name="test", result=True)

View File

@ -0,0 +1,48 @@
"""Event Middleware tests"""
from django.shortcuts import reverse
from rest_framework.test import APITestCase
from authentik.core.models import Application, User
from authentik.events.models import Event, EventAction
class TestEventsMiddleware(APITestCase):
"""Test Event Middleware"""
def setUp(self) -> None:
super().setUp()
self.user = User.objects.get(username="akadmin")
self.client.force_login(self.user)
def test_create(self):
"""Test model creation event"""
self.client.post(
reverse("authentik_api:application-list"),
data={"name": "test-create", "slug": "test-create"},
)
self.assertTrue(Application.objects.filter(name="test-create").exists())
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_CREATED,
context__model__model_name="application",
context__model__app="authentik_core",
context__model__name="test-create",
).exists()
)
def test_delete(self):
"""Test model creation event"""
Application.objects.create(name="test-delete", slug="test-delete")
self.client.delete(
reverse("authentik_api:application-detail", kwargs={"slug": "test-delete"})
)
self.assertFalse(Application.objects.filter(name="test").exists())
self.assertTrue(
Event.objects.filter(
action=EventAction.MODEL_DELETED,
context__model__model_name="application",
context__model__app="authentik_core",
context__model__name="test-delete",
).exists()
)

View File

@ -0,0 +1,90 @@
"""Notification tests"""
from unittest.mock import MagicMock, patch
from django.test import TestCase
from authentik.core.models import Group, User
from authentik.events.models import (
Event,
EventAction,
NotificationRule,
NotificationTransport,
)
from authentik.policies.event_matcher.models import EventMatcherPolicy
from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
class TestEventsNotifications(TestCase):
"""Test Event Notifications"""
def setUp(self) -> None:
self.group = Group.objects.create(name="test-group")
self.user = User.objects.create(name="test-user")
self.group.users.add(self.user)
self.group.save()
def test_trigger_empty(self):
"""Test trigger without any policies attached"""
transport = NotificationTransport.objects.create(name="transport")
trigger = NotificationRule.objects.create(name="trigger", group=self.group)
trigger.transports.add(transport)
trigger.save()
execute_mock = MagicMock()
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(execute_mock.call_count, 0)
def test_trigger_single(self):
"""Test simple transport triggering"""
transport = NotificationTransport.objects.create(name="transport")
trigger = NotificationRule.objects.create(name="trigger", group=self.group)
trigger.transports.add(transport)
trigger.save()
matcher = EventMatcherPolicy.objects.create(
name="matcher", action=EventAction.CUSTOM_PREFIX
)
PolicyBinding.objects.create(target=trigger, policy=matcher, order=0)
execute_mock = MagicMock()
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(execute_mock.call_count, 1)
def test_trigger_no_group(self):
"""Test trigger without group"""
trigger = NotificationRule.objects.create(name="trigger")
matcher = EventMatcherPolicy.objects.create(
name="matcher", action=EventAction.CUSTOM_PREFIX
)
PolicyBinding.objects.create(target=trigger, policy=matcher, order=0)
execute_mock = MagicMock()
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(execute_mock.call_count, 0)
def test_policy_error_recursive(self):
"""Test Policy error which would cause recursion"""
transport = NotificationTransport.objects.create(name="transport")
NotificationRule.objects.filter(name__startswith="default").delete()
trigger = NotificationRule.objects.create(name="trigger", group=self.group)
trigger.transports.add(transport)
trigger.save()
matcher = EventMatcherPolicy.objects.create(
name="matcher", action=EventAction.CUSTOM_PREFIX
)
PolicyBinding.objects.create(target=trigger, policy=matcher, order=0)
execute_mock = MagicMock()
passes = MagicMock(side_effect=PolicyException)
with patch(
"authentik.policies.event_matcher.models.EventMatcherPolicy.passes", passes
):
with patch(
"authentik.events.models.NotificationTransport.send", execute_mock
):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(passes.call_count, 1)

View File

@ -1,9 +0,0 @@
"""authentik events urls"""
from django.urls import path
from authentik.events.views import EventListView
urlpatterns = [
# Event Log
path("log/", EventListView.as_view(), name="log"),
]

View File

@ -5,12 +5,15 @@ from typing import Any, Dict, Optional
from uuid import UUID
from django.contrib.auth.models import AnonymousUser
from django.core.handlers.wsgi import WSGIRequest
from django.db import models
from django.db.models.base import Model
from django.http.request import HttpRequest
from django.views.debug import SafeExceptionReporterFilter
from guardian.utils import get_anonymous_user
from authentik.core.models import User
from authentik.policies.types import PolicyRequest
# Special keys which are *not* cleaned, even when the default filter
# is matched
@ -74,13 +77,22 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
final_dict = {}
for key, value in source.items():
if is_dataclass(value):
# Because asdict calls `copy.deepcopy(obj)` on everything thats not tuple/dict,
# and deepcopy doesn't work with HttpRequests (neither django nor rest_framework).
# Currently, the only dataclass that actually holds an http request is a PolicyRequest
if isinstance(value, PolicyRequest):
value.http_request = None
value = asdict(value)
if isinstance(value, dict):
final_dict[key] = sanitize_dict(value)
elif isinstance(value, User):
final_dict[key] = sanitize_dict(get_user(value))
elif isinstance(value, models.Model):
final_dict[key] = sanitize_dict(model_to_dict(value))
elif isinstance(value, UUID):
final_dict[key] = value.hex
elif isinstance(value, (HttpRequest, WSGIRequest)):
continue
else:
final_dict[key] = value
return final_dict

View File

@ -1,30 +0,0 @@
"""authentik Event administration"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from guardian.mixins import PermissionListMixin
from authentik.admin.views.utils import SearchListMixin, UserPaginateListMixin
from authentik.events.models import Event
class EventListView(
PermissionListMixin,
LoginRequiredMixin,
SearchListMixin,
UserPaginateListMixin,
ListView,
):
"""Show list of all invitations"""
model = Event
template_name = "events/list.html"
permission_required = "authentik_events.view_event"
ordering = "-created"
search_fields = [
"user",
"action",
"app",
"context",
"client_ip",
]

View File

@ -78,6 +78,8 @@ class FlowViewSet(ModelViewSet):
queryset = Flow.objects.all()
serializer_class = FlowSerializer
lookup_field = "slug"
search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"]
@swagger_auto_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, methods=["get"])

View File

@ -1,9 +1,11 @@
"""flow exceptions"""
from authentik.lib.sentry import SentryIgnoredException
class FlowNonApplicableException(BaseException):
class FlowNonApplicableException(SentryIgnoredException):
"""Flow does not apply to current user (denied by policy)."""
class EmptyFlowException(BaseException):
class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages."""

View File

@ -7,7 +7,7 @@ from time import time
from django import db
from django.core.management.base import BaseCommand
from django.test import RequestFactory
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik import __version__
from authentik.core.models import User

View File

@ -3,7 +3,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional
from django.http.request import HttpRequest
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.flows.models import Stage

View File

@ -8,7 +8,7 @@ from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.policies.models import PolicyBindingModel

View File

@ -6,7 +6,7 @@ from django.core.cache import cache
from django.http import HttpRequest
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.events.models import cleanse_dict
@ -21,6 +21,7 @@ PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso"
PLAN_CONTEXT_REDIRECT = "redirect"
PLAN_CONTEXT_APPLICATION = "application"
PLAN_CONTEXT_SOURCE = "source"
def cache_key(flow: Flow, user: Optional[User] = None) -> str:
@ -119,7 +120,7 @@ class FlowPlanner:
engine.build()
result = engine.result
if not result.passing:
raise FlowNonApplicableException(result.messages)
raise FlowNonApplicableException(",".join(result.messages))
# User is passing so far, check if we have a cached plan
cached_plan_key = cache_key(self.flow, user)
cached_plan = cache.get(cached_plan_key, None)

View File

@ -2,7 +2,7 @@
from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver
from structlog import get_logger
from structlog.stdlib import get_logger
LOGGER = get_logger()

View File

@ -15,7 +15,7 @@ from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import TemplateView, View
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict

View File

@ -5,13 +5,15 @@ from contextlib import contextmanager
from glob import glob
from json import dumps
from time import time
from typing import Any, Dict
from typing import Any
from urllib.parse import urlparse
import yaml
from django.conf import ImproperlyConfigured
from django.http import HttpRequest
from authentik import __version__
SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob(
"/etc/authentik/config.d/*.yml", recursive=True
)
@ -19,10 +21,9 @@ ENV_PREFIX = "AUTHENTIK"
ENVIRONMENT = os.getenv(f"{ENV_PREFIX}_ENV", "local")
def context_processor(request: HttpRequest) -> Dict[str, Any]:
def context_processor(request: HttpRequest) -> dict[str, Any]:
"""Context Processor that injects config object into every template"""
kwargs = {"config": CONFIG.raw}
return kwargs
return {"config": CONFIG.raw, "ak_version": __version__}
class ConfigLoader:

View File

@ -21,6 +21,17 @@ error_reporting:
environment: customer
send_pii: false
# Global email settings
email:
host: localhost
port: 25
username: ""
password: ""
use_tls: false
use_ssl: false
timeout: 10
from: authentik@localhost
outposts:
docker_image_base: "beryju/authentik" # this is prepended to -proxy:version

View File

@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
from requests import Session
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import User
@ -60,7 +60,7 @@ class BaseEvaluator:
@staticmethod
def expr_func_is_group_member(user: User, **group_filters) -> bool:
"""Check if `user` is member of group with name `group_name`"""
return user.groups.filter(**group_filters).exists()
return user.ak_groups.filter(**group_filters).exists()
def wrap_expression(self, expression: str, params: Iterable[str]) -> str:
"""Wrap expression in a function, call it, and save the result as `result`"""

View File

@ -11,7 +11,7 @@ from ldap3.core.exceptions import LDAPException
from redis.exceptions import ConnectionError as RedisConnectionError
from redis.exceptions import RedisError, ResponseError
from rest_framework.exceptions import APIException
from structlog import get_logger
from structlog.stdlib import get_logger
from websockets.exceptions import WebSocketException
LOGGER = get_logger()

View File

@ -8,7 +8,7 @@ from django.http.request import HttpRequest
from django.template import Context
from django.templatetags.static import static
from django.utils.html import escape, mark_safe
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.lib.config import CONFIG

View File

@ -5,7 +5,7 @@ from django.http import HttpResponse
from django.shortcuts import redirect, reverse
from django.urls import NoReverseMatch
from django.utils.http import urlencode
from structlog import get_logger
from structlog.stdlib import get_logger
LOGGER = get_logger()

View File

@ -12,7 +12,7 @@ from django.db import ProgrammingError
from docker.constants import DEFAULT_UNIX_SOCKET
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
from structlog import get_logger
from structlog.stdlib import get_logger
LOGGER = get_logger()

View File

@ -8,7 +8,7 @@ from channels.exceptions import DenyConnection
from dacite import from_dict
from dacite.data import Data
from guardian.shortcuts import get_objects_for_user
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.channels import AuthJsonConsumer
from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState

View File

@ -1,21 +1,32 @@
"""Base Controller"""
from typing import Dict, List
from dataclasses import dataclass
from structlog import get_logger
from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import Outpost, OutpostServiceConnection
FIELD_MANAGER = "goauthentik.io"
class ControllerException(SentryIgnoredException):
"""Exception raised when anything fails during controller run"""
@dataclass
class DeploymentPort:
"""Info about deployment's single port."""
port: int
name: str
protocol: str
class BaseController:
"""Base Outpost deployment controller"""
deployment_ports: Dict[str, int]
deployment_ports: list[DeploymentPort]
outpost: Outpost
connection: OutpostServiceConnection
@ -24,14 +35,14 @@ class BaseController:
self.outpost = outpost
self.connection = connection
self.logger = get_logger()
self.deployment_ports = {}
self.deployment_ports = []
# pylint: disable=invalid-name
def up(self):
"""Called by scheduled task to reconcile deployment/service/etc"""
raise NotImplementedError
def up_with_logs(self) -> List[str]:
def up_with_logs(self) -> list[str]:
"""Call .up() but capture all log output and return it."""
with capture_logs() as logs:
self.up()

View File

@ -68,7 +68,10 @@ class DockerController(BaseController):
"image": image_name,
"name": f"authentik-proxy-{self.outpost.uuid.hex}",
"detach": True,
"ports": {x: x for _, x in self.deployment_ports.items()},
"ports": {
f"{port.port}/{port.protocol.lower()}": port.port
for port in self.deployment_ports
},
"environment": self._get_env(),
"labels": self._get_labels(),
}
@ -139,7 +142,10 @@ class DockerController(BaseController):
def get_static_deployment(self) -> str:
"""Generate docker-compose yaml for proxy, version 3.5"""
ports = [f"{x}:{x}" for _, x in self.deployment_ports.items()]
ports = [
f"{port.port}:{port.port}/{port.protocol.lower()}"
for port in self.deployment_ports
]
image_prefix = CONFIG.y("outposts.docker_image_base")
compose = {
"version": "3.5",
@ -154,6 +160,7 @@ class DockerController(BaseController):
),
"AUTHENTIK_TOKEN": self.outpost.token.key,
},
"labels": self._get_labels(),
}
},
}

View File

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Generic, TypeVar
from kubernetes.client import V1ObjectMeta
from kubernetes.client.rest import ApiException
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik import __version__
from authentik.lib.sentry import SentryIgnoredException
@ -93,7 +93,8 @@ class KubernetesObjectReconciler(Generic[T]):
def reconcile(self, current: T, reference: T):
"""Check what operations should be done, should be raised as
ReconcileTrigger"""
raise NotImplementedError
if current.metadata.labels != reference.metadata.labels:
raise NeedsUpdate()
def create(self, reference: T):
"""API Wrapper to create object"""

View File

@ -18,6 +18,7 @@ from kubernetes.client import (
from authentik import __version__
from authentik.lib.config import CONFIG
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsUpdate,
@ -43,6 +44,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
def reconcile(self, current: V1Deployment, reference: V1Deployment):
super().reconcile(current, reference)
if current.spec.replicas != reference.spec.replicas:
raise NeedsUpdate()
if (
@ -63,8 +65,14 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
"""Get deployment object for outpost"""
# Generate V1ContainerPort objects
container_ports = []
for port_name, port in self.controller.deployment_ports.items():
container_ports.append(V1ContainerPort(container_port=port, name=port_name))
for port in self.controller.deployment_ports:
container_ports.append(
V1ContainerPort(
container_port=port.port,
name=port.name,
protocol=port.protocol.upper(),
)
)
meta = self.get_object_meta(name=self.name)
secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
image_prefix = CONFIG.y("outposts.docker_image_base")
@ -118,7 +126,9 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
)
def create(self, reference: V1Deployment):
return self.api.create_namespaced_deployment(self.namespace, reference)
return self.api.create_namespaced_deployment(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Deployment):
return self.api.delete_namespaced_deployment(
@ -130,5 +140,8 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
def update(self, current: V1Deployment, reference: V1Deployment):
return self.api.patch_namespaced_deployment(
current.metadata.name, self.namespace, reference
current.metadata.name,
self.namespace,
reference,
field_manager=FIELD_MANAGER,
)

View File

@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Secret
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsUpdate,
@ -30,6 +31,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
def reconcile(self, current: V1Secret, reference: V1Secret):
super().reconcile(current, reference)
for key in reference.data.keys():
if current.data[key] != reference.data[key]:
raise NeedsUpdate()
@ -51,7 +53,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
)
def create(self, reference: V1Secret):
return self.api.create_namespaced_secret(self.namespace, reference)
return self.api.create_namespaced_secret(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Secret):
return self.api.delete_namespaced_secret(
@ -63,5 +67,8 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
def update(self, current: V1Secret, reference: V1Secret):
return self.api.patch_namespaced_secret(
current.metadata.name, self.namespace, reference
current.metadata.name,
self.namespace,
reference,
field_manager=FIELD_MANAGER,
)

View File

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsUpdate,
@ -25,6 +26,7 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
return f"authentik-outpost-{self.controller.outpost.uuid.hex}"
def reconcile(self, current: V1Service, reference: V1Service):
super().reconcile(current, reference)
if len(current.spec.ports) != len(reference.spec.ports):
raise NeedsUpdate()
for port in reference.spec.ports:
@ -35,8 +37,15 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
"""Get deployment object for outpost"""
meta = self.get_object_meta(name=self.name)
ports = []
for port_name, port in self.controller.deployment_ports.items():
ports.append(V1ServicePort(name=port_name, port=port))
for port in self.controller.deployment_ports:
ports.append(
V1ServicePort(
name=port.name,
port=port.port,
protocol=port.protocol.upper(),
target_port=port.port,
)
)
selector_labels = DeploymentReconciler(self.controller).get_pod_meta()
return V1Service(
metadata=meta,
@ -44,7 +53,9 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
)
def create(self, reference: V1Service):
return self.api.create_namespaced_service(self.namespace, reference)
return self.api.create_namespaced_service(
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: V1Service):
return self.api.delete_namespaced_service(
@ -56,5 +67,8 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
def update(self, current: V1Service, reference: V1Service):
return self.api.patch_namespaced_service(
current.metadata.name, self.namespace, reference
current.metadata.name,
self.namespace,
reference,
field_manager=FIELD_MANAGER,
)

View File

@ -24,7 +24,7 @@ from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict
from model_utils.managers import InheritanceManager
from packaging.version import LegacyVersion, Version, parse
from structlog import get_logger
from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import __version__

View File

@ -2,17 +2,24 @@
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.models import Outpost
from authentik.outposts.models import Outpost, OutpostServiceConnection
from authentik.outposts.tasks import outpost_post_save, outpost_pre_delete
LOGGER = get_logger()
UPDATE_TRIGGERING_MODELS = (
Outpost,
OutpostServiceConnection,
Provider,
CertificateKeyPair,
)
@receiver(post_save)
# pylint: disable=unused-argument
def post_save_update(sender, instance: Model, **_):
"""If an Outpost is saved, Ensure that token is created/updated
@ -22,6 +29,8 @@ def post_save_update(sender, instance: Model, **_):
return
if instance.__module__ == "__fake__":
return
if sender not in UPDATE_TRIGGERING_MODELS:
return
outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)

View File

@ -6,9 +6,9 @@ from channels.layers import get_channel_layer
from django.core.cache import cache
from django.db.models.base import Model
from django.utils.text import slugify
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.reflection import path_to_class
from authentik.outposts.controllers.base import ControllerException
from authentik.outposts.models import (
@ -49,9 +49,15 @@ def outpost_service_connection_state(connection_pk: Any):
@CELERY_APP.task(bind=True, base=MonitoredTask)
def outpost_service_connection_monitor(self: MonitoredTask):
"""Regularly check the state of Outpost Service Connections"""
for connection in OutpostServiceConnection.objects.all():
connections = OutpostServiceConnection.objects.all()
for connection in connections.iterator():
outpost_service_connection_state.delay(connection.pk)
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,
[f"Successfully updated {len(connections)} connections."],
)
)
@CELERY_APP.task(bind=True, base=MonitoredTask)
@ -124,14 +130,12 @@ def outpost_post_save(model_class: str, model_pk: Any):
_ = instance.token
LOGGER.debug("Trigger reconcile for outpost")
outpost_controller.delay(instance.pk)
return
if isinstance(instance, (OutpostModel, Outpost)):
LOGGER.debug(
"triggering outpost update from outpostmodel/outpost", instance=instance
)
outpost_send_update(instance)
return
if isinstance(instance, OutpostServiceConnection):
LOGGER.debug("triggering ServiceConnection state update", instance=instance)

View File

@ -7,7 +7,7 @@ from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import BaseSerializer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult

View File

@ -1,5 +1,6 @@
"""authentik policy engine"""
from multiprocessing import Pipe, set_start_method
from enum import Enum
from multiprocessing import Pipe, current_process
from multiprocessing.connection import Connection
from typing import Iterator, List, Optional
@ -7,7 +8,7 @@ from django.core.cache import cache
from django.http import HttpRequest
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
@ -15,9 +16,7 @@ from authentik.policies.process import PolicyProcess, cache_key
from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
# This is only really needed for macOS, because Python 3.8 changed the default to spawn
# spawn causes issues with objects that aren't picklable, and also the django setup
set_start_method("fork")
CURRENT_PROCESS = current_process()
class PolicyProcessInfo:
@ -37,12 +36,23 @@ class PolicyProcessInfo:
self.result = None
class PolicyEngineMode(Enum):
"""Decide how results of multiple policies should be combined."""
MODE_AND = "and"
MODE_OR = "or"
class PolicyEngine:
"""Orchestrate policy checking, launch tasks and return result"""
use_cache: bool
request: PolicyRequest
mode: PolicyEngineMode
# Allow objects with no policies attached to pass
empty_result: bool
__pbm: PolicyBindingModel
__cached_policies: List[PolicyResult]
__processes: List[PolicyProcessInfo]
@ -52,10 +62,15 @@ class PolicyEngine:
def __init__(
self, pbm: PolicyBindingModel, user: User, request: HttpRequest = None
):
self.mode = PolicyEngineMode.MODE_AND
# For backwards compatibility, set empty_result to true
# objects with no policies attached will pass.
self.empty_result = True
if not isinstance(pbm, PolicyBindingModel): # pragma: no cover
raise ValueError(f"{pbm} is not instance of PolicyBindingModel")
self.__pbm = pbm
self.request = PolicyRequest(user)
self.request.obj = pbm
if request:
self.request.http_request = request
self.__cached_policies = []
@ -65,8 +80,10 @@ class PolicyEngine:
def _iter_bindings(self) -> Iterator[PolicyBinding]:
"""Make sure all Policies are their respective classes"""
return PolicyBinding.objects.filter(target=self.__pbm, enabled=True).order_by(
"order"
return (
PolicyBinding.objects.filter(target=self.__pbm, enabled=True)
.order_by("order")
.iterator()
)
def _check_policy_type(self, policy: Policy):
@ -98,14 +115,19 @@ class PolicyEngine:
LOGGER.debug("P_ENG: Evaluating policy", policy=binding.policy)
our_end, task_end = Pipe(False)
task = PolicyProcess(binding, self.request, task_end)
task.daemon = False
LOGGER.debug("P_ENG: Starting Process", policy=binding.policy)
task.start()
if not CURRENT_PROCESS._config.get("daemon"):
task.run()
else:
task.start()
self.__processes.append(
PolicyProcessInfo(process=task, connection=our_end, binding=binding)
)
# If all policies are cached, we have an empty list here.
for proc_info in self.__processes:
proc_info.process.join(proc_info.binding.timeout)
if proc_info.process.is_alive():
proc_info.process.join(proc_info.binding.timeout)
# Only call .recv() if no result is saved, otherwise we just deadlock here
if not proc_info.result:
proc_info.result = proc_info.connection.recv()
@ -118,24 +140,19 @@ class PolicyEngine:
x.result for x in self.__processes if x.result
]
all_results = list(process_results + self.__cached_policies)
final_result = PolicyResult(False)
final_result.messages = []
final_result.source_results = all_results
if len(all_results) < self.__expected_result_count: # pragma: no cover
raise AssertionError("Got less results than polices")
for result in all_results:
LOGGER.debug(
"P_ENG: result", passing=result.passing, messages=result.messages
)
if result.messages:
final_result.messages.extend(result.messages)
if not result.passing:
final_result.messages = tuple(final_result.messages)
final_result.passing = False
return final_result
final_result.messages = tuple(final_result.messages)
final_result.passing = True
return final_result
# No results, no policies attached -> passing
if len(all_results) == 0:
return PolicyResult(self.empty_result)
passing = False
if self.mode == PolicyEngineMode.MODE_AND:
passing = all([x.passing for x in all_results])
if self.mode == PolicyEngineMode.MODE_OR:
passing = any([x.passing for x in all_results])
result = PolicyResult(passing)
result.messages = tuple([y for x in all_results for y in x.messages])
return result
@property
def passing(self) -> bool:

View File

@ -0,0 +1,25 @@
"""Event Matcher Policy API"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.policies.event_matcher.models import EventMatcherPolicy
from authentik.policies.forms import GENERAL_SERIALIZER_FIELDS
class EventMatcherPolicySerializer(ModelSerializer):
"""Event Matcher Policy Serializer"""
class Meta:
model = EventMatcherPolicy
fields = GENERAL_SERIALIZER_FIELDS + [
"action",
"client_ip",
"app",
]
class EventMatcherPolicyViewSet(ModelViewSet):
"""Event Matcher Policy Viewset"""
queryset = EventMatcherPolicy.objects.all()
serializer_class = EventMatcherPolicySerializer

View File

@ -0,0 +1,11 @@
"""authentik Event Matcher policy app config"""
from django.apps import AppConfig
class AuthentikPoliciesEventMatcherConfig(AppConfig):
"""authentik Event Matcher policy app config"""
name = "authentik.policies.event_matcher"
label = "authentik_policies_event_matcher"
verbose_name = "authentik Policies.Event Matcher"

View File

@ -0,0 +1,25 @@
"""authentik Event Matcher Policy forms"""
from django import forms
from django.utils.translation import gettext_lazy as _
from authentik.policies.event_matcher.models import EventMatcherPolicy
from authentik.policies.forms import GENERAL_FIELDS
class EventMatcherPolicyForm(forms.ModelForm):
"""EventMatcherPolicy Form"""
class Meta:
model = EventMatcherPolicy
fields = GENERAL_FIELDS + [
"action",
"client_ip",
"app",
]
widgets = {
"name": forms.TextInput(),
"client_ip": forms.TextInput(),
}
labels = {"client_ip": _("Client IP")}

View File

@ -0,0 +1,70 @@
# Generated by Django 3.1.4 on 2020-12-24 10:32
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_policies", "0004_policy_execution_logging"),
]
operations = [
migrations.CreateModel(
name="EventMatcherPolicy",
fields=[
(
"policy_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policy",
),
),
(
"action",
models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
(
"property_mapping_exception",
"Property Mapping Exception",
),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
),
),
("client_ip", models.TextField(blank=True)),
],
options={
"verbose_name": "Group Membership Policy",
"verbose_name_plural": "Group Membership Policies",
},
bases=("authentik_policies.policy",),
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 3.1.4 on 2020-12-30 20:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="action",
field=models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
),
),
]

View File

@ -0,0 +1,111 @@
# Generated by Django 3.1.4 on 2021-01-10 19:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0002_auto_20201230_2046"),
]
operations = [
migrations.AddField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
(
"authentik.policies.group_membership",
"authentik Policies.Group Membership",
),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.saml", "authentik Sources.SAML"),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
(
"authentik.stages.identification",
"authentik Stages.Identification",
),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.stages.otp_static", "authentik OTP.Static"),
("authentik.stages.otp_time", "authentik OTP.Time"),
("authentik.stages.otp_validate", "authentik OTP.Validate"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
migrations.AlterField(
model_name="eventmatcherpolicy",
name="action",
field=models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
help_text="Match created events with this action type. When left empty, all action types will be matched.",
),
),
migrations.AlterField(
model_name="eventmatcherpolicy",
name="client_ip",
field=models.TextField(
blank=True,
help_text="Matches Event's Client IP (strict matching, for network matching use an Expression Policy)",
),
),
]

View File

@ -0,0 +1,79 @@
# Generated by Django 3.1.4 on 2021-01-12 21:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
]
operations = [
migrations.AlterModelOptions(
name="eventmatcherpolicy",
options={
"verbose_name": "Event Matcher Policy",
"verbose_name_plural": "Event Matcher Policies",
},
),
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
(
"authentik.policies.group_membership",
"authentik Policies.Group Membership",
),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.saml", "authentik Sources.SAML"),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
(
"authentik.stages.identification",
"authentik Stages.Identification",
),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.stages.otp_static", "authentik Stages.OTP.Static"),
("authentik.stages.otp_time", "authentik Stages.OTP.Time"),
("authentik.stages.otp_validate", "authentik Stages.OTP.Validate"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -0,0 +1,88 @@
"""Event Matcher models"""
from typing import Type
from django.apps import apps
from django.db import models
from django.forms import ModelForm
from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from authentik.events.models import Event, EventAction
from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult
def app_choices() -> list[tuple[str, str]]:
"""Get a list of all installed applications that create events.
Returns a list of tuples containing (dotted.app.path, name)"""
choices = []
for app in apps.get_app_configs():
if app.label.startswith("authentik"):
choices.append((app.name, app.verbose_name))
return choices
class EventMatcherPolicy(Policy):
"""Passes when Event matches selected criteria."""
action = models.TextField(
choices=EventAction.choices,
blank=True,
help_text=_(
(
"Match created events with this action type. "
"When left empty, all action types will be matched."
)
),
)
app = models.TextField(
choices=app_choices(),
blank=True,
default="",
help_text=_(
(
"Match events created by selected application. "
"When left empty, all applications are matched."
)
),
)
client_ip = models.TextField(
blank=True,
help_text=_(
(
"Matches Event's Client IP (strict matching, "
"for network matching use an Expression Policy)"
)
),
)
@property
def serializer(self) -> BaseSerializer:
from authentik.policies.event_matcher.api import (
EventMatcherPolicySerializer,
)
return EventMatcherPolicySerializer
@property
def form(self) -> Type[ModelForm]:
from authentik.policies.event_matcher.forms import EventMatcherPolicyForm
return EventMatcherPolicyForm
def passes(self, request: PolicyRequest) -> PolicyResult:
if "event" not in request.context:
return PolicyResult(False)
event: Event = request.context["event"]
if event.action == self.action:
return PolicyResult(True, "Action matched.")
if event.client_ip == self.client_ip:
return PolicyResult(True, "Client IP matched.")
if event.app == self.app:
return PolicyResult(True, "App matched.")
return PolicyResult(False)
class Meta:
verbose_name = _("Event Matcher Policy")
verbose_name_plural = _("Event Matcher Policies")

View File

@ -0,0 +1,68 @@
"""event_matcher tests"""
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.events.models import Event, EventAction
from authentik.policies.event_matcher.models import EventMatcherPolicy
from authentik.policies.types import PolicyRequest
class TestEventMatcherPolicy(TestCase):
"""EventMatcherPolicy tests"""
def test_match_action(self):
"""Test match action"""
event = Event.new(EventAction.LOGIN)
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
action=EventAction.LOGIN
)
response = policy.passes(request)
self.assertTrue(response.passing)
self.assertTupleEqual(response.messages, ("Action matched.",))
def test_match_client_ip(self):
"""Test match client_ip"""
event = Event.new(EventAction.LOGIN)
event.client_ip = "1.2.3.4"
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
client_ip="1.2.3.4"
)
response = policy.passes(request)
self.assertTrue(response.passing)
self.assertTupleEqual(response.messages, ("Client IP matched.",))
def test_match_app(self):
"""Test match app"""
event = Event.new(EventAction.LOGIN)
event.app = "foo"
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(app="foo")
response = policy.passes(request)
self.assertTrue(response.passing)
self.assertTupleEqual(response.messages, ("App matched.",))
def test_drop(self):
"""Test drop event"""
event = Event.new(EventAction.LOGIN)
event.client_ip = "1.2.3.4"
request = PolicyRequest(get_anonymous_user())
request.context["event"] = event
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
client_ip="1.2.3.5"
)
response = policy.passes(request)
self.assertFalse(response.passing)
def test_invalid(self):
"""Test passing event"""
request = PolicyRequest(get_anonymous_user())
policy: EventMatcherPolicy = EventMatcherPolicy.objects.create(
client_ip="1.2.3.4"
)
response = policy.passes(request)
self.assertFalse(response.passing)

View File

@ -1,6 +1,14 @@
"""policy exceptions"""
from typing import Optional
from authentik.lib.sentry import SentryIgnoredException
class PolicyException(SentryIgnoredException):
"""Exception that should be raised during Policy Evaluation, and can be recovered from."""
src_exc: Optional[Exception] = None
def __init__(self, src_exc: Optional[Exception] = None) -> None:
super().__init__()
self.src_exc = src_exc

View File

@ -7,7 +7,7 @@ from django.forms import ModelForm
from django.utils.timezone import now
from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult

View File

@ -1,16 +1,14 @@
"""authentik expression policy evaluator"""
from ipaddress import ip_address, ip_network
from traceback import format_tb
from typing import TYPE_CHECKING, List, Optional
from django.http import HttpRequest
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.events.utils import model_to_dict, sanitize_dict
from authentik.flows.planner import PLAN_CONTEXT_SSO
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip
from authentik.policies.exceptions import PolicyException
from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
@ -57,32 +55,26 @@ class PolicyEvaluator(BaseEvaluator):
def handle_error(self, exc: Exception, expression_source: str):
"""Exception Handler"""
error_string = "\n".join(format_tb(exc.__traceback__) + [str(exc)])
event = Event.new(
EventAction.POLICY_EXCEPTION,
expression=expression_source,
error=error_string,
request=self._context["request"],
)
if self.policy:
event.context["model"] = sanitize_dict(model_to_dict(self.policy))
if "http_request" in self._context:
event.from_http(self._context["http_request"])
else:
event.set_user(self._context["request"].user)
event.save()
# So, this is a bit questionable. Essentially, we are edit the stacktrace
# so the user only sees information relevant to them
# and none of our surrounding error handling
exc.__traceback__ = exc.__traceback__.tb_next
raise PolicyException(exc)
def evaluate(self, expression_source: str) -> PolicyResult:
"""Parse and evaluate expression. Policy is expected to return a truthy object.
Messages can be added using 'do ak_message()'."""
try:
result = super().evaluate(expression_source)
except PolicyException as exc:
# PolicyExceptions should be propagated back to the process,
# which handles recording and returning a correct result
raise exc
except Exception as exc: # pylint: disable=broad-except
LOGGER.warning("Expression error", exc=exc)
return PolicyResult(False, str(exc))
else:
policy_result = PolicyResult(False)
policy_result.messages = tuple(self._messages)
policy_result = PolicyResult(False, *self._messages)
if result is None:
LOGGER.warning(
"Expression policy returned None",

View File

@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.events.models import Event, EventAction
from authentik.policies.exceptions import PolicyException
from authentik.policies.expression.evaluator import PolicyEvaluator
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.types import PolicyRequest
@ -44,30 +44,8 @@ class TestEvaluator(TestCase):
template = ";"
evaluator = PolicyEvaluator("test")
evaluator.set_policy_request(self.request)
result = evaluator.evaluate(template)
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ("invalid syntax (test, line 3)",))
self.assertTrue(
Event.objects.filter(
action=EventAction.POLICY_EXCEPTION,
context__expression=template,
).exists()
)
def test_undefined(self):
"""test undefined result"""
template = "{{ foo.bar }}"
evaluator = PolicyEvaluator("test")
evaluator.set_policy_request(self.request)
result = evaluator.evaluate(template)
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ("name 'foo' is not defined",))
self.assertTrue(
Event.objects.filter(
action=EventAction.POLICY_EXCEPTION,
context__expression=template,
).exists()
)
with self.assertRaises(PolicyException):
evaluator.evaluate(template)
def test_validate(self):
"""test validate"""

View File

@ -7,7 +7,7 @@ from django.forms import ModelForm
from django.utils.translation import gettext as _
from requests import get
from rest_framework.serializers import BaseSerializer
from structlog import get_logger
from structlog.stdlib import get_logger
from authentik.policies.models import Policy, PolicyResult
from authentik.policies.types import PolicyRequest

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