Compare commits

...

172 Commits

Author SHA1 Message Date
33cdbd7776 release: 2021.2.1-rc1 2021-02-06 20:10:50 +01:00
18bc54214d web: increase height of multi-select 2021-02-06 19:19:57 +01:00
db7e9f9b95 sources/ldap: set default group property mapping 2021-02-06 19:17:39 +01:00
a885247d36 docs: update release notes for 2021.2 2021-02-06 19:09:42 +01:00
91282c7bd8 web: add page for Proxy Provider 2021-02-06 18:57:25 +01:00
830b8bcd5b web: add page for OAuth2 Provider 2021-02-06 18:39:15 +01:00
0f5e6d0d8c api: add dark theme for API Browser 2021-02-06 18:09:24 +01:00
6aa6615608 web: add view page for SAML Provider 2021-02-06 18:07:13 +01:00
91d6a3c8c7 providers/*: simplify provider API 2021-02-06 17:31:29 +01:00
a6ac82c492 *: rewrite managed objects, use nullable text flag instead of boolean as uid (#533) 2021-02-06 15:56:21 +00:00
05d777c373 Merge pull request #528 from BeryJu/ldap-groupOfNames
sources/ldap: support group to user memberships
2021-02-06 16:07:36 +01:00
32cf960053 sources/ldap: add property_mappings_group to make group mapping more customisable 2021-02-06 15:27:07 +01:00
83bf639926 sources/ldap: use both entryDN and dn (for active-directory) 2021-02-05 15:17:57 +01:00
2717742bd2 sources/ldap: don't remove users from group which were not synced from AD 2021-02-05 15:17:20 +01:00
ef70e93bbd Merge branch 'master' into ldap-groupOfNames 2021-02-05 14:52:39 +01:00
478d3430eb sources/ldap: use openldap tests for entire sync 2021-02-05 14:29:22 +01:00
9c1ade59e9 sources/ldap: add more flatten to user sync, start adding tests for OpenLDAP 2021-02-05 13:36:27 +01:00
fadf746234 managed: allow for matching on multiple interfaces 2021-02-05 13:18:44 +01:00
397dfc29f1 sources/ldap: change default object filters to use objectClass= instead of objectCategory 2021-02-05 11:43:39 +01:00
b0e3b8b39d sources/ldap: use entryDN attribute from ldap3 as opposed to implicit DN attribute 2021-02-05 11:43:13 +01:00
df9ae796d4 build(deps): bump boto3 from 1.17.1 to 1.17.2 (#529)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.1 to 1.17.2.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.1...1.17.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-05 11:00:31 +01:00
dfdad5388f build(deps): bump @sentry/tracing from 6.0.4 to 6.1.0 in /web (#531)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.0.4 to 6.1.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.0.4...6.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-05 11:00:19 +01:00
c38ea69bdd build(deps-dev): bump autopep8 from 1.5.4 to 1.5.5 (#530)
Bumps [autopep8](https://github.com/hhatto/autopep8) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/hhatto/autopep8/releases)
- [Commits](https://github.com/hhatto/autopep8/compare/v1.5.4...v1.5.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-05 09:52:46 +01:00
dca6f43858 build(deps): bump @sentry/browser from 6.0.4 to 6.1.0 in /web (#532)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.0.4 to 6.1.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.0.4...6.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-05 09:52:33 +01:00
51cbb7cc8e ci: fix warning when setting branchName in PR 2021-02-05 00:38:08 +01:00
1f8130e685 events: improve information sent in notification emails 2021-02-04 21:44:59 +01:00
580d59e921 web: add EventInfoPage 2021-02-04 21:28:01 +01:00
e639d8ab56 sources/ldap: add case when group does not have uniqueness attribute 2021-02-04 21:18:49 +01:00
9f478bb46a web: fix type warnings 2021-02-04 21:10:13 +01:00
7a16f97908 web: add ak-expand for event list to show full context 2021-02-04 20:59:18 +01:00
dd8c1eeb52 web: add ak-expand 2021-02-04 20:56:40 +01:00
005b4d8dda sources/ldap: fix linting issues 2021-02-04 20:36:05 +01:00
de2d8b2d85 providers/oauth2: pass application to configuration error event 2021-02-04 20:35:37 +01:00
7d107991a2 sources/ldap: fix count for membership, fix wrong attribute being searched 2021-02-04 20:22:28 +01:00
14dc420747 sources/ldap: rewrite group membership syncing 2021-02-04 20:06:42 +01:00
89dc4db30b sources/ldap: load operational attributes (#526) 2021-02-04 12:37:55 +01:00
cc3fccb27e sources/ldap: use dn attribute for distinguishedName, ignore users with no distinguishedName
closes #527
2021-02-04 12:10:57 +01:00
add20de8de providers/*: fix api linting issues 2021-02-04 10:27:55 +01:00
7e2a471903 web: fix linting issues 2021-02-04 10:22:14 +01:00
9ca9e67ffa web: fix pagination not working correctly sometimes, fix pagination not showing when changing pages 2021-02-04 10:09:19 +01:00
178417fe67 web: start implementing provider list 2021-02-04 10:09:19 +01:00
53f002a123 events: allow searching by event id 2021-02-04 10:09:19 +01:00
c7c387eb38 providers/*: add assigned application name and slug 2021-02-04 10:09:19 +01:00
1b3760a4b7 events: don't log successful system tasks 2021-02-04 10:09:18 +01:00
704a502089 build(deps): bump @types/codemirror from 0.0.107 to 0.0.108 in /web (#523)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.107 to 0.0.108.
- [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-02-04 09:36:52 +01:00
3b12ef80eb build(deps): bump boto3 from 1.17.0 to 1.17.1 (#522)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.0...1.17.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-04 09:36:38 +01:00
1101810fea admin: show more details for policy testing 2021-02-03 22:09:46 +01:00
1ab5289e2e admin: add test view for property mappings 2021-02-03 21:58:56 +01:00
ac24fc9ce3 web: add javascript mode to codemirror 2021-02-03 21:58:30 +01:00
4b24b185f2 admin: fix context not being passed correctly to policy test view 2021-02-03 21:40:03 +01:00
ea0ba5ae30 stages/password: use form.add_error 2021-02-03 21:39:03 +01:00
44686de74e docs: prepare 2021.2 releases 2021-02-03 21:29:13 +01:00
b74c08620a admin: add link to changelog to update events 2021-02-03 21:19:51 +01:00
e25d03d8f4 Managed objects (#519)
* managed: add base manager and Ops

* core: use ManagedModel for Token and PropertyMapping

* providers/saml: implement managed objects for SAML Provider

* sources/ldap: migrate to managed

* providers/oauth2: migrate to managed

* providers/proxy: migrate to managed

* *: load .managed in apps

* managed: add reconcile task, run on startup

* providers/oauth2: fix import path for managed

* providers/saml: don't set FriendlyName when mapping is none

* *: use ObjectManager in tests to ensure objects exist

* ci: use vmImage ubuntu-latest

* providers/saml: add new mapping for username and user id

* tests: remove docker proxy

* tests/e2e: use updated attribute names

* docs: update SAML docs

* tests/e2e: fix remaining saml cases

* outposts: make tokens as managed

* *: make PropertyMapping SerializerModel

* web: add page for property-mappings

* web: add codemirror to common_styles because codemirror

* docs: fix member-of in nextcloud

* docs: nextcloud add admin

* web: fix refresh reloading data two times

* web: add loading lock to table to prevent double loads

* web: add ability to use null in QueryArgs (value will be skipped)

* web: add hide option to property mappings

* web: fix linting
2021-02-03 21:18:31 +01:00
f8f26d2a23 build(deps): bump rollup from 2.38.3 to 2.38.4 in /web (#520)
Bumps [rollup](https://github.com/rollup/rollup) from 2.38.3 to 2.38.4.
- [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.3...v2.38.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-03 09:32:12 +01:00
1f2e177e3e build(deps): bump boto3 from 1.16.63 to 1.17.0 (#521)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.63 to 1.17.0.
- [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.63...1.17.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-03 09:32:01 +01:00
cfed41439e events: add send_once flag to send webhooks only once 2021-02-02 19:34:55 +01:00
3ac148d01c events: only title for slack webhook 2021-02-02 19:18:51 +01:00
3e696d6ac8 flows: use global logger for stored plans 2021-02-02 17:29:03 +01:00
0114bc0d6a flows: fix lint errors 2021-02-02 17:02:02 +01:00
c60934f9b1 flows: fix benchmark using wrong context 2021-02-02 16:27:21 +01:00
09bdcfaab0 flows: optimise logging 2021-02-02 16:27:03 +01:00
624206281e policies: optimise logging 2021-02-02 16:12:41 +01:00
4d7e64c48c web: adjust trace sample rate 2021-02-02 15:50:29 +01:00
3d112e7688 root: use filtering_bound_logger for speed improvements 2021-02-02 15:43:44 +01:00
3c4ff65a01 stages/consent: fix wrong widget for expire 2021-02-02 15:01:33 +01:00
d7f54ce5d5 build(deps): bump @sentry/tracing from 6.0.3 to 6.0.4 in /web (#515)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.0.3 to 6.0.4.
- [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.3...6.0.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 11:23:47 +01:00
bc55c97fa2 build(deps): bump @sentry/browser from 6.0.3 to 6.0.4 in /web (#516)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.0.3 to 6.0.4.
- [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.3...6.0.4)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 11:21:41 +01:00
d9a907e39e build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#518)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.14.1 to 4.14.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.14.2/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 11:21:28 +01:00
8616647045 build(deps): bump rollup from 2.38.2 to 2.38.3 in /web (#517)
Bumps [rollup](https://github.com/rollup/rollup) from 2.38.2 to 2.38.3.
- [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.2...v2.38.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 10:03:18 +01:00
4d861e2830 build(deps-dev): bump @typescript-eslint/parser in /web (#514)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.14.1 to 4.14.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.14.2/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 10:03:03 +01:00
881730f52e build(deps): bump django from 3.1.5 to 3.1.6 (#513)
Bumps [django](https://github.com/django/django) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.5...3.1.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-02 10:02:49 +01:00
e78577d470 build(deps): bump @sentry/tracing from 6.0.2 to 6.0.3 in /web (#511)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.0.2 to 6.0.3.
- [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.2...6.0.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 10:15:59 +01:00
d502f4d77d build(deps): bump boto3 from 1.16.62 to 1.16.63 (#507)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.62 to 1.16.63.
- [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.62...1.16.63)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:12:43 +01:00
3c5f7deba9 build(deps): bump packaging from 20.8 to 20.9 (#506)
Bumps [packaging](https://github.com/pypa/packaging) from 20.8 to 20.9.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/20.8...20.9)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:12:30 +01:00
b61334c482 build(deps-dev): bump eslint from 7.18.0 to 7.19.0 in /web (#508)
Bumps [eslint](https://github.com/eslint/eslint) from 7.18.0 to 7.19.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.18.0...v7.19.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:12:20 +01:00
eb762632d0 build(deps-dev): bump @rollup/plugin-typescript in /web (#509)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins) from 8.1.0 to 8.1.1.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Commits](https://github.com/rollup/plugins/compare/typescript-v8.1.0...typescript-v8.1.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:12:10 +01:00
6a882249aa build(deps): bump rollup from 2.38.1 to 2.38.2 in /web (#510)
Bumps [rollup](https://github.com/rollup/rollup) from 2.38.1 to 2.38.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.38.1...v2.38.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:11:58 +01:00
94f6bbd431 build(deps): bump @sentry/browser from 6.0.2 to 6.0.3 in /web (#512)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.0.2 to 6.0.3.
- [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.2...6.0.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-02-01 09:11:42 +01:00
3926ee9eb6 core: clear application cache upon application creation 2021-01-30 18:12:14 +01:00
7fbf915e0a policies: fix application cached not being cleared correctly 2021-01-30 18:12:01 +01:00
5af9e8c05d core: improve application caching 2021-01-30 18:03:44 +01:00
7c0c453d9f web: fix new provider dropdown being cut off 2021-01-30 12:38:33 +01:00
d8ae56ed19 providers/saml: fix imported provider not saving properties correctly 2021-01-30 12:33:27 +01:00
a9a65ceca6 Merge branch 'version-2021.1' 2021-01-29 10:45:55 +01:00
c11fd884b8 docs: separate 2021.1 fixes by patch release 2021-01-29 10:45:50 +01:00
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
191 changed files with 4263 additions and 1413 deletions

View File

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

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/authentik:2021.1.1-rc1
-t beryju/authentik:2021.2.1-rc1
-t beryju/authentik:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:2021.1.1-rc1
run: docker push beryju/authentik:2021.2.1-rc1
- 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:2021.1.1-rc1 \
-t beryju/authentik-proxy:2021.2.1-rc1 \
-t beryju/authentik-proxy:latest \
-f Dockerfile .
-f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:2021.1.1-rc1
run: docker push beryju/authentik-proxy:2021.2.1-rc1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest
build-static:
@ -69,11 +69,11 @@ jobs:
cd web/
docker build \
--no-cache \
-t beryju/authentik-static:2021.1.1-rc1 \
-t beryju/authentik-static:2021.2.1-rc1 \
-t beryju/authentik-static:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:2021.1.1-rc1
run: docker push beryju/authentik-static:2021.2.1-rc1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest
test-release:
@ -107,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 2021.1.1-rc1
tagName: 2021.2.1-rc1
environment: beryjuorg-prod

314
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,24 @@
},
"boto3": {
"hashes": [
"sha256:b5052144034e490358c659d0e480c17a4e604fd3aee9a97ddfe6e361a245a4a5",
"sha256:efd6c96c98900e9fbf217f13cb58f59b793e51f69a1ce61817eefd31f17c6ef5"
"sha256:1a282c1cd7d5028cbb3a75d747df32162295253f55d263ac85840e264830963b"
],
"index": "pypi",
"version": "==1.16.55"
"version": "==1.17.2"
},
"botocore": {
"hashes": [
"sha256:760d0c16c1474c2a46e3fa45e33ae7457b5cab7410737ab1692340ade764cc73",
"sha256:b34327d84b3bb5620fb54603677a9a973b167290c2c1e7ab69c4a46b201c6d46"
"sha256:7442fdbbdc841bfac7f94f92ecb807de070e32ed205743eb72d4ea27c5e8e778",
"sha256:bf587b044983a91a0124cc133ff167b8528c19fbbc8f0b956d9a1ac256cad7d7"
],
"version": "==1.19.55"
"version": "==1.20.2"
},
"cachetools": {
"hashes": [
"sha256:3796e1de094f0eaca982441c92ce96c68c89cced4cd97721ab297ea4b16db90e",
"sha256:c6b07a6ded8c78bf36730b3dc452dfff7d95f2a12a2fed856b1a0cb13ca78c61"
"sha256:1d9d5f567be80f7c07d765e21b814326d78c61eb0c3a637dffc0e5d1796cb2e2",
"sha256:f469e29e7aa4cff64d8de4aad95ce76de8ea1125a16c68e0d93f65c3c3dc92e9"
],
"version": "==4.2.0"
"version": "==4.2.1"
},
"celery": {
"hashes": [
@ -127,6 +126,7 @@
"sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009",
"sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03",
"sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b",
"sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e",
"sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909",
"sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53",
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
@ -265,11 +265,11 @@
},
"django": {
"hashes": [
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
"sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
"sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
],
"index": "pypi",
"version": "==3.1.5"
"version": "==3.1.6"
},
"django-cors-middleware": {
"hashes": [
@ -397,10 +397,10 @@
},
"google-auth": {
"hashes": [
"sha256:0b0e026b412a0ad096e753907559e4bdb180d9ba9f68dd9036164db4fdc4ad2e",
"sha256:ce752cc51c31f479dbf9928435ef4b07514b20261b021c7383bee4bda646acb8"
"sha256:008e23ed080674f69f9d2d7d80db4c2591b9bb307d136cea7b3bc129771d211d",
"sha256:514e39f4190ca972200ba33876da5a8857c5665f2b4ccc36c8b8ee21228aae80"
],
"version": "==1.24.0"
"version": "==1.25.0"
},
"gunicorn": {
"hashes": [
@ -522,10 +522,10 @@
},
"jinja2": {
"hashes": [
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
"sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
"sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
],
"version": "==2.11.2"
"version": "==2.11.3"
},
"jmespath": {
"hashes": [
@ -558,11 +558,11 @@
},
"ldap3": {
"hashes": [
"sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0"
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
],
"index": "pypi",
"version": "==2.8.1"
"version": "==2.9"
},
"lxml": {
"hashes": [
@ -614,8 +614,12 @@
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
@ -624,24 +628,39 @@
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
],
"version": "==1.1.1"
},
@ -687,11 +706,11 @@
},
"packaging": {
"hashes": [
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
],
"index": "pypi",
"version": "==20.8"
"version": "==20.9"
},
"prometheus-client": {
"hashes": [
@ -702,10 +721,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7",
"sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5"
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
],
"version": "==3.0.10"
"version": "==3.0.14"
},
"psycopg2-binary": {
"hashes": [
@ -900,29 +919,37 @@
},
"pytz": {
"hashes": [
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
],
"version": "==2020.5"
"version": "==2021.1"
},
"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": [
@ -1072,12 +1099,11 @@
"secure"
],
"hashes": [
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
],
"index": "pypi",
"markers": null,
"version": "==1.26.2"
"version": "==1.26.3"
},
"uvicorn": {
"extras": [
@ -1267,10 +1293,11 @@
},
"autopep8": {
"hashes": [
"sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"
"sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
"sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
],
"index": "pypi",
"version": "==1.5.4"
"version": "==1.5.5"
},
"bandit": {
"hashes": [
@ -1319,66 +1346,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:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
"sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f",
"sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"
],
"index": "pypi",
"version": "==3.1.5"
"version": "==3.1.6"
},
"django-debug-toolbar": {
"hashes": [
@ -1479,11 +1506,11 @@
},
"packaging": {
"hashes": [
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
],
"index": "pypi",
"version": "==20.8"
"version": "==20.9"
},
"pathspec": {
"hashes": [
@ -1592,11 +1619,11 @@
},
"pytest": {
"hashes": [
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
],
"index": "pypi",
"version": "==6.2.1"
"version": "==6.2.2"
},
"pytest-django": {
"hashes": [
@ -1608,29 +1635,37 @@
},
"pytz": {
"hashes": [
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
],
"version": "==2020.5"
"version": "==2021.1"
},
"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": [
@ -1707,17 +1742,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": [
@ -1788,12 +1823,11 @@
"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

@ -1,2 +1,2 @@
"""authentik"""
__version__ = "2021.1.1-rc1"
__version__ = "2021.2.1-rc1"

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

@ -1,17 +1,22 @@
"""authentik admin tasks"""
import re
from django.core.cache import cache
from django.core.validators import URLValidator
from packaging.version import parse
from requests import RequestException, get
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()
VERSION_CACHE_KEY = "authentik_latest_version"
VERSION_CACHE_TIMEOUT = 2 * 60 * 60 # 2 hours
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
# Chop of the first ^ because we want to search the entire string
URL_FINDER = URLValidator.regex.pattern[1:]
@CELERY_APP.task(bind=True, base=MonitoredTask)
@ -39,7 +44,10 @@ def update_latest_version(self: MonitoredTask):
context__new_version=upstream_version,
).exists():
return
Event.new(EventAction.UPDATE_AVAILABLE, new_version=upstream_version).save()
event_dict = {"new_version": upstream_version}
if match := re.search(URL_FINDER, data.get("body", "")):
event_dict["message"] = f"Changelog: {match.group()}"
Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save()
except (RequestException, IndexError) as exc:
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))

View File

@ -3,7 +3,42 @@
{% load i18n %}
{% block above_form %}
<h1>{% blocktrans with policy=policy %}Test policy {{ policy }}{% endblocktrans %}</h1>
<h1>{% blocktrans with policy=policy %}Test {{ policy }}{% endblocktrans %}</h1>
{% endblock %}
{% block beneath_form %}
{% if result %}
<div class="pf-c-form__group ">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="context-1">
<span class="pf-c-form__label-text">{% trans 'Passing' %}</span>
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">{{ result.passing|yesno:"Yes,No" }}</span>
</div>
</div>
</div>
<div class="pf-c-form__group ">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="context-1">
<span class="pf-c-form__label-text">{% trans 'Messages' %}</span>
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
<ul>
{% for m in result.messages %}
<li><span class="pf-c-form__label-text">{{ m }}</span></li>
{% empty %}
<li><span class="pf-c-form__label-text">-</span></li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block action %}

View File

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

View File

@ -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

@ -32,7 +32,8 @@ REQUEST_MOCK_VALID = Mock(
return_value=MockResponse(
200,
"""{
"tag_name": "version/99999999.9999999"
"tag_name": "version/99999999.9999999",
"body": "https://goauthentik.io/test"
}""",
)
)
@ -50,7 +51,9 @@ class TestAdminTasks(TestCase):
self.assertEqual(cache.get(VERSION_CACHE_KEY), "99999999.9999999")
self.assertTrue(
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE, context__new_version="99999999.9999999"
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
context__message="Changelog: https://goauthentik.io/test",
).exists()
)
# test that a consecutive check doesn't create a duplicate event
@ -58,7 +61,9 @@ class TestAdminTasks(TestCase):
self.assertEqual(
len(
Event.objects.filter(
action=EventAction.UPDATE_AVAILABLE, context__new_version="99999999.9999999"
action=EventAction.UPDATE_AVAILABLE,
context__new_version="99999999.9999999",
context__message="Changelog: https://goauthentik.io/test",
)
),
1,

View File

@ -238,11 +238,6 @@ urlpatterns = [
name="flow-delete",
),
# Property Mappings
path(
"property-mappings/",
property_mappings.PropertyMappingListView.as_view(),
name="property-mappings",
),
path(
"property-mappings/create/",
property_mappings.PropertyMappingCreateView.as_view(),
@ -258,6 +253,11 @@ urlpatterns = [
property_mappings.PropertyMappingDeleteView.as_view(),
name="property-mapping-delete",
),
path(
"property-mappings/<uuid:pk>/test/",
property_mappings.PropertyMappingTestView.as_view(),
name="property-mapping-test",
),
# Users
path("users/", users.UserListView.as_view(), name="users"),
path("users/create/", users.UserCreateView.as_view(), name="user-create"),

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

@ -28,7 +28,7 @@ class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
cache.delete_many(keys)
LOGGER.debug("Cleared Policy cache", keys=len(keys))
# Also delete user application cache
keys = user_app_cache_key("*")
keys = cache.keys(user_app_cache_key("*"))
cache.delete_many(keys)
return super().post(request, *args, **kwargs)

View File

@ -1,13 +1,11 @@
"""authentik Policy administration"""
from typing import Any, Dict
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import QuerySet
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
@ -99,7 +97,7 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
template_name = "administration/policy/test.html"
object = None
def get_object(self, queryset=None) -> QuerySet:
def get_object(self, queryset=None) -> Policy:
return (
Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
)
@ -118,12 +116,10 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
p_request = PolicyRequest(user)
p_request.http_request = self.request
p_request.context = form.cleaned_data
p_request.context = form.cleaned_data.get("context", {})
proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None)
result = proc.execute()
if result.passing:
messages.success(self.request, _("User successfully passed policy."))
else:
messages.error(self.request, _("User didn't pass policy."))
return self.render_to_response(self.get_context_data(form=form, result=result))
context = self.get_context_data(form=form)
context["result"] = result
return self.render_to_response(context)

View File

@ -1,41 +1,29 @@
"""authentik PropertyMapping administration"""
from json import dumps
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import FormView
from django.views.generic.detail import DetailView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import (
BackSuccessUrlMixin,
DeleteMessageView,
InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.models import PropertyMapping
class PropertyMappingListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all property_mappings"""
model = PropertyMapping
permission_required = "authentik_core.view_propertymapping"
template_name = "administration/property_mapping/list.html"
ordering = "name"
search_fields = ["name", "expression"]
class PropertyMappingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
@ -81,3 +69,44 @@ class PropertyMappingDeleteView(
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:property-mappings")
success_message = _("Successfully deleted Property Mapping")
class PropertyMappingTestView(
LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView
):
"""View to test property mappings"""
model = PropertyMapping
form_class = PolicyTestForm
permission_required = "authentik_core.view_propertymapping"
template_name = "administration/property_mapping/test.html"
object = None
def get_object(self, queryset=None) -> PropertyMapping:
return (
PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
.select_subclasses()
.first()
)
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs["property_mapping"] = self.get_object()
return super().get_context_data(**kwargs)
def post(self, *args, **kwargs) -> HttpResponse:
self.object = self.get_object()
return super().post(*args, **kwargs)
def form_valid(self, form: PolicyTestForm) -> HttpResponse:
mapping = self.get_object()
user = form.cleaned_data.get("user")
context = self.get_context_data(form=form)
try:
result = mapping.evaluate(
user, self.request, **form.cleaned_data.get("context", {})
)
context["result"] = dumps(result, indent=4)
except Exception as exc: # pylint: disable=broad-except
context["result"] = str(exc)
return self.render_to_response(context)

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

@ -1,7 +1,31 @@
{% extends "rest_framework/base.html" %}
{% block title %}{% if name %}{{ name }} {% endif %}authentik{% endblock %}
{% block branding %}
<span class='navbar-brand'>
authentik
</span>
{% endblock %}
{% block style %}
{{ block.super }}
<style>
body {
background-color: #18191a;
color: #fafafa;
}
.prettyprint {
background-color: #1c1e21;
color: #fafafa;
border: 1px solid #2b2e33;
}
.pln {
color: #fafafa;
}
.well {
background-color: #1c1e21;
border: 1px solid #2b2e33;
}
</style>
{% endblock %}

View File

@ -4,9 +4,6 @@ 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.stdlib import get_logger
LOGGER = get_logger()
def admin_autoregister(app: AppConfig):
@ -20,5 +17,4 @@ def admin_autoregister(app: AppConfig):
for _app in apps.get_app_configs():
if _app.label.startswith("authentik_"):
LOGGER.debug("Registering application for dj-admin", application=_app.label)
admin_autoregister(_app)

View File

@ -11,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
@ -18,6 +19,8 @@ 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"""
@ -74,19 +77,35 @@ class ApplicationViewSet(ModelViewSet):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
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:
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)
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
if not allowed_applications:
allowed_applications = []
for application in queryset:
engine = PolicyEngine(application, self.request.user, self.request)
engine.build()
if engine.passing:
allowed_applications.append(application)
cache.set(user_app_cache_key(self.request.user.pk), allowed_applications)
should_cache = request.GET.get("search", "") == ""
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

@ -2,22 +2,36 @@
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.models import PropertyMapping
class PropertyMappingSerializer(ModelSerializer):
class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
"""PropertyMapping Serializer"""
__type__ = SerializerMethodField(method_name="get_type")
object_type = SerializerMethodField(method_name="get_type")
def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("propertymapping", "")
def to_representation(self, instance: PropertyMapping):
# pyright: reportGeneralTypeIssues=false
if instance.__class__ == PropertyMapping:
return super().to_representation(instance)
return instance.serializer(instance=instance).data
class Meta:
model = PropertyMapping
fields = ["pk", "name", "expression", "__type__"]
fields = [
"pk",
"name",
"expression",
"object_type",
"verbose_name",
"verbose_name_plural",
]
class PropertyMappingViewSet(ReadOnlyModelViewSet):
@ -25,6 +39,11 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet):
queryset = PropertyMapping.objects.none()
serializer_class = PropertyMappingSerializer
search_fields = [
"name",
]
filterset_fields = {"managed": ["isnull"]}
ordering = ["name"]
def get_queryset(self):
return PropertyMapping.objects.select_subclasses()

View File

@ -1,4 +1,5 @@
"""Provider API Views"""
from rest_framework.fields import ReadOnlyField
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ModelViewSet
@ -9,18 +10,15 @@ from authentik.core.models import Provider
class ProviderSerializer(ModelSerializer, MetaNameSerializer):
"""Provider Serializer"""
assigned_application_slug = ReadOnlyField(source="application.slug")
assigned_application_name = ReadOnlyField(source="application.name")
object_type = SerializerMethodField()
def get_object_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("provider", "")
def to_representation(self, instance: Provider):
# pyright: reportGeneralTypeIssues=false
if instance.__class__ == Provider:
return super().to_representation(instance)
return instance.serializer(instance=instance).data
class Meta:
model = Provider
@ -31,6 +29,8 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
"authorization_flow",
"property_mappings",
"object_type",
"assigned_application_slug",
"assigned_application_name",
"verbose_name",
"verbose_name_plural",
]
@ -44,6 +44,10 @@ class ProviderViewSet(ModelViewSet):
filterset_fields = {
"application": ["isnull"],
}
search_fields = [
"name",
"application__name",
]
def get_queryset(self):
return Provider.objects.select_subclasses()

View File

@ -1,4 +1,6 @@
"""authentik core app config"""
from importlib import import_module
from django.apps import AppConfig
@ -9,3 +11,6 @@ class AuthentikCoreConfig(AppConfig):
label = "authentik_core"
verbose_name = "authentik Core"
mountpoint = ""
def ready(self):
import_module("authentik.core.signals")

View File

@ -0,0 +1,35 @@
# Generated by Django 3.1.4 on 2021-01-30 18:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0016_auto_20201202_2234"),
]
operations = [
migrations.AddField(
model_name="propertymapping",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
verbose_name="Managed by authentik",
unique=True,
),
),
migrations.AddField(
model_name="token",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
verbose_name="Managed by authentik",
unique=True,
),
),
]

View File

@ -22,6 +22,7 @@ from authentik.core.signals import password_changed
from authentik.core.types import UILoginButton
from authentik.flows.models import Flow
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.managed.models import ManagedModel
from authentik.policies.models import PolicyBindingModel
LOGGER = get_logger()
@ -313,7 +314,7 @@ class TokenIntents(models.TextChoices):
INTENT_RECOVERY = "recovery"
class Token(ExpiringModel):
class Token(ManagedModel, ExpiringModel):
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -341,7 +342,7 @@ class Token(ExpiringModel):
]
class PropertyMapping(models.Model):
class PropertyMapping(SerializerModel, ManagedModel):
"""User-defined key -> x mapping which can be used by providers to expose extra data."""
pm_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -355,6 +356,11 @@ class PropertyMapping(models.Model):
"""Return Form class used to edit this object"""
raise NotImplementedError
@property
def serializer(self) -> Type[Serializer]:
"""Get serializer for this model"""
raise NotImplementedError
def evaluate(
self, user: Optional[User], request: Optional[HttpRequest], **kwargs
) -> Any:

View File

@ -1,5 +1,24 @@
"""authentik core signals"""
from django.core.cache import cache
from django.core.signals import Signal
from django.db.models.signals import post_save
from django.dispatch import receiver
# Arguments: user: User, password: str
password_changed = Signal()
@receiver(post_save)
# pylint: disable=unused-argument
def post_save_application(sender, instance, created: bool, **_):
"""Clear user's application cache upon application creation"""
from authentik.core.models import Application
from authentik.core.api.applications import user_app_cache_key
if sender != Application:
return
if not created:
return
# Also delete user application cache
keys = cache.keys(user_app_cache_key("*"))
cache.delete_many(keys)

View File

@ -11,7 +11,7 @@ from django.utils.timezone import now
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

@ -50,6 +50,7 @@ class EventViewSet(ReadOnlyModelViewSet):
serializer_class = EventSerializer
ordering = ["-created"]
search_fields = [
"event_uuid",
"user",
"action",
"app",

View File

@ -15,6 +15,7 @@ class NotificationTransportForm(forms.ModelForm):
"name",
"mode",
"webhook_url",
"send_once",
]
widgets = {
"name": forms.TextInput(),

View File

@ -0,0 +1,52 @@
# Generated by Django 3.1.6 on 2021-02-02 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0011_notification_rules_default_v1"),
]
operations = [
migrations.AddField(
model_name="notificationtransport",
name="send_once",
field=models.BooleanField(
default=False,
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
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"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task 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

@ -22,7 +22,6 @@ 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.tasks import send_mail
from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger("authentik.events")
@ -57,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"
@ -182,6 +184,12 @@ class NotificationTransport(models.Model):
mode = models.TextField(choices=TransportMode.choices)
webhook_url = models.TextField(blank=True)
send_once = models.BooleanField(
default=False,
help_text=_(
"Only send notification once, for example when sending a webhook into a chat channel."
),
)
def send(self, notification: "Notification") -> list[str]:
"""Send notification to user, called from async task"""
@ -252,7 +260,6 @@ class NotificationTransport(models.Model):
}
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()
@ -265,24 +272,33 @@ class NotificationTransport(models.Model):
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
)
subject = "authentik Notification: "
key_value = {}
if notification.event:
subject += notification.event.action
for key, value in notification.event.context.items():
if not isinstance(value, str):
continue
key_value[key] = value
else:
subject += notification.body[:75]
mail = TemplateEmailMessage(
subject=f"authentik Notification: {body_trunc}",
template_name="email/setup.html",
subject=subject,
template_name="email/generic.html",
to=[notification.user.email],
template_context={
"title": subject,
"body": notification.body,
"key_value": key_value,
},
)
# 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) as exc:
except (SMTPException, ConnectionError, OSError) as exc:
raise NotificationTransportError from exc
def __str__(self) -> str:

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"""
@ -138,6 +140,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

@ -9,7 +9,7 @@ from authentik.events.models import (
NotificationTransport,
NotificationTransportError,
)
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
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
@ -65,15 +65,17 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
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():
for transport in trigger.transports.all():
for user in trigger.group.users.all():
LOGGER.debug("created notif")
notification = Notification.objects.create(
severity=trigger.severity, body=event.summary, event=event, user=user
)
notification_transport.apply_async(
args=[notification.pk, transport.pk], queue="authentik_events"
)
if transport.send_once:
break
@CELERY_APP.task(

View File

@ -8,6 +8,7 @@ from authentik.core.models import Group, User
from authentik.events.models import (
Event,
EventAction,
Notification,
NotificationRule,
NotificationTransport,
)
@ -21,7 +22,7 @@ class TestEventsNotifications(TestCase):
def setUp(self) -> None:
self.group = Group.objects.create(name="test-group")
self.user = User.objects.create(name="test-user")
self.user = User.objects.create(name="test-user", username="test")
self.group.users.add(self.user)
self.group.save()
@ -87,4 +88,27 @@ class TestEventsNotifications(TestCase):
"authentik.events.models.NotificationTransport.send", execute_mock
):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(passes.call_count, 0)
self.assertEqual(passes.call_count, 1)
def test_transport_once(self):
"""Test transport's send_once"""
user2 = User.objects.create(name="test2-user", username="test2")
self.group.users.add(user2)
self.group.save()
transport = NotificationTransport.objects.create(
name="transport", send_once=True
)
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()
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
Event.new(EventAction.CUSTOM_PREFIX).save()
self.assertEqual(Notification.objects.count(), 1)

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

@ -1,6 +1,6 @@
"""authentik benchmark command"""
from csv import DictWriter
from multiprocessing import Manager, Process, cpu_count
from multiprocessing import Manager, cpu_count, get_context
from sys import stdout
from time import time
@ -15,9 +15,11 @@ from authentik.flows.models import Flow
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
LOGGER = get_logger()
FORK_CTX = get_context("fork")
PROCESS_CLASS = FORK_CTX.Process
class FlowPlanProcess(Process): # pragma: no cover
class FlowPlanProcess(PROCESS_CLASS): # pragma: no cover
"""Test process which executes flow planner"""
def __init__(self, index, return_dict, flow, user) -> None:

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.stdlib import get_logger
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import User
from authentik.events.models import cleanse_dict
@ -16,7 +16,6 @@ from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.policies.engine import PolicyEngine
LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso"
PLAN_CONTEXT_REDIRECT = "redirect"
@ -88,10 +87,13 @@ class FlowPlanner:
flow: Flow
_logger: BoundLogger
def __init__(self, flow: Flow):
self.use_cache = True
self.allow_empty_flows = False
self.flow = flow
self._logger = get_logger().bind(flow=flow)
def plan(
self, request: HttpRequest, default_context: Optional[Dict[str, Any]] = None
@ -103,7 +105,9 @@ class FlowPlanner:
span.set_data("flow", self.flow)
span.set_data("request", request)
LOGGER.debug("f(plan): Starting planning process", flow=self.flow)
self._logger.debug(
"f(plan): starting planning process",
)
# Bit of a workaround here, if there is a pending user set in the default context
# we use that user for our cache key
# to make sure they don't get the generic response
@ -120,20 +124,21 @@ 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)
if cached_plan and self.use_cache:
LOGGER.debug(
"f(plan): Taking plan from cache",
flow=self.flow,
self._logger.debug(
"f(plan): taking plan from cache",
key=cached_plan_key,
)
# Reset the context as this isn't factored into caching
cached_plan.context = default_context or {}
return cached_plan
LOGGER.debug("f(plan): building plan", flow=self.flow)
self._logger.debug(
"f(plan): building plan",
)
plan = self._build_plan(user, request, default_context)
cache.set(cache_key(self.flow, user), plan)
if not plan.stages and not self.allow_empty_flows:
@ -165,39 +170,34 @@ class FlowPlanner:
stage = binding.stage
marker = StageMarker()
if binding.evaluate_on_plan:
LOGGER.debug(
self._logger.debug(
"f(plan): evaluating on plan",
stage=binding.stage,
flow=self.flow,
)
engine = PolicyEngine(binding, user, request)
engine.request.context = plan.context
engine.build()
if engine.passing:
LOGGER.debug(
"f(plan): Stage passing",
self._logger.debug(
"f(plan): stage passing",
stage=binding.stage,
flow=self.flow,
)
else:
stage = None
else:
LOGGER.debug(
self._logger.debug(
"f(plan): not evaluating on plan",
stage=binding.stage,
flow=self.flow,
)
if binding.re_evaluate_policies and stage:
LOGGER.debug(
"f(plan): Stage has re-evaluate marker",
self._logger.debug(
"f(plan): stage has re-evaluate marker",
stage=binding.stage,
flow=self.flow,
)
marker = ReevaluateMarker(binding=binding, user=user)
if stage:
plan.append(stage, marker)
LOGGER.debug(
"f(plan): Finished building",
flow=self.flow,
self._logger.debug(
"f(plan): finished building",
)
return plan

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.stdlib import get_logger
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict
@ -49,45 +49,48 @@ class FlowExecutorView(View):
current_stage: Stage
current_stage_view: View
_logger: BoundLogger
def setup(self, request: HttpRequest, flow_slug: str):
super().setup(request, flow_slug=flow_slug)
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
self._logger = get_logger().bind(flow_slug=flow_slug)
def handle_invalid_flow(self, exc: BaseException) -> HttpResponse:
"""When a flow is non-applicable check if user is on the correct domain"""
if NEXT_ARG_NAME in self.request.GET:
if not is_url_absolute(self.request.GET.get(NEXT_ARG_NAME)):
LOGGER.debug("f(exec): Redirecting to next on fail")
self._logger.debug("f(exec): Redirecting to next on fail")
return redirect(self.request.GET.get(NEXT_ARG_NAME))
message = exc.__doc__ if exc.__doc__ else str(exc)
return self.stage_invalid(error_message=message)
# pylint: disable=unused-argument
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex:
LOGGER.warning(
self._logger.warning(
"f(exec): Found existing plan for other flow, deleteing plan",
flow_slug=flow_slug,
)
# Existing plan is deleted from session and instance
self.plan = None
self.cancel()
LOGGER.debug("f(exec): Continuing existing plan", flow_slug=flow_slug)
self._logger.debug("f(exec): Continuing existing plan")
# Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan:
LOGGER.debug(
"f(exec): No active Plan found, initiating planner", flow_slug=flow_slug
)
self._logger.debug("f(exec): No active Plan found, initiating planner")
try:
self.plan = self._initiate_plan()
except FlowNonApplicableException as exc:
LOGGER.warning("f(exec): Flow not applicable to current user", exc=exc)
self._logger.warning(
"f(exec): Flow not applicable to current user", exc=exc
)
return to_stage_response(self.request, self.handle_invalid_flow(exc))
except EmptyFlowException as exc:
LOGGER.warning("f(exec): Flow is empty", exc=exc)
self._logger.warning("f(exec): Flow is empty", exc=exc)
# To match behaviour with loading an empty flow plan from cache,
# we don't show an error message here, but rather call _flow_done()
return self._flow_done()
@ -95,10 +98,10 @@ class FlowExecutorView(View):
# as it hasn't been successfully passed yet
next_stage = self.plan.next(self.request)
if not next_stage:
LOGGER.debug("f(exec): no more stages, flow is done.")
self._logger.debug("f(exec): no more stages, flow is done.")
return self._flow_done()
self.current_stage = next_stage
LOGGER.debug(
self._logger.debug(
"f(exec): Current stage",
current_stage=self.current_stage,
flow_slug=self.flow.slug,
@ -112,32 +115,30 @@ class FlowExecutorView(View):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""pass get request to current stage"""
LOGGER.debug(
self._logger.debug(
"f(exec): Passing GET",
view_class=class_to_path(self.current_stage_view.__class__),
stage=self.current_stage,
flow_slug=self.flow.slug,
)
try:
stage_response = self.current_stage_view.get(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
LOGGER.exception(exc)
self._logger.exception(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""pass post request to current stage"""
LOGGER.debug(
self._logger.debug(
"f(exec): Passing POST",
view_class=class_to_path(self.current_stage_view.__class__),
stage=self.current_stage,
flow_slug=self.flow.slug,
)
try:
stage_response = self.current_stage_view.post(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
LOGGER.exception(exc)
self._logger.exception(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
def _initiate_plan(self) -> FlowPlan:
@ -163,26 +164,23 @@ class FlowExecutorView(View):
def stage_ok(self) -> HttpResponse:
"""Callback called by stages upon successful completion.
Persists updated plan and context to session."""
LOGGER.debug(
self._logger.debug(
"f(exec): Stage ok",
stage_class=class_to_path(self.current_stage_view.__class__),
flow_slug=self.flow.slug,
)
self.plan.pop()
self.request.session[SESSION_KEY_PLAN] = self.plan
if self.plan.stages:
LOGGER.debug(
self._logger.debug(
"f(exec): Continuing with next stage",
reamining=len(self.plan.stages),
flow_slug=self.flow.slug,
)
return redirect_with_qs(
"authentik_flows:flow-executor", self.request.GET, **self.kwargs
)
# User passed all stages
LOGGER.debug(
self._logger.debug(
"f(exec): User passed all stages",
flow_slug=self.flow.slug,
context=cleanse_dict(self.plan.context),
)
return self._flow_done()
@ -193,7 +191,7 @@ class FlowExecutorView(View):
Optionally, an exception can be passed, which will be shown if the current user
is a superuser."""
LOGGER.debug("f(exec): Stage invalid", flow_slug=self.flow.slug)
self._logger.debug("f(exec): Stage invalid")
self.cancel()
response = AccessDeniedResponse(
self.request, template="flows/denied_shell.html"

View File

@ -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

@ -1,7 +1,6 @@
"""logging helpers"""
from logging import Logger
from os import getpid
from typing import Callable
# pylint: disable=unused-argument
@ -9,15 +8,3 @@ def add_process_id(logger: Logger, method_name: str, event_dict):
"""Add the current process ID"""
event_dict["pid"] = getpid()
return event_dict
def add_common_fields(environment: str) -> Callable:
"""Add a common field to easily search for authentik logs"""
def add_common_field(logger: Logger, method_name: str, event_dict):
"""Add a common field to easily search for authentik logs"""
event_dict["app"] = "authentik"
event_dict["app_environment"] = environment
return event_dict
return add_common_field

View File

@ -59,6 +59,5 @@ def before_send(event, hint):
if "exc_info" in hint:
_, exc_value, _ = hint["exc_info"]
if isinstance(exc_value, ignored_classes):
LOGGER.info("Supressing error %r", exc_value)
return None
return event

View File

16
authentik/managed/apps.py Normal file
View File

@ -0,0 +1,16 @@
"""authentik Managed app"""
from django.apps import AppConfig
class AuthentikManagedConfig(AppConfig):
"""authentik Managed app"""
name = "authentik.managed"
label = "authentik_Managed"
verbose_name = "authentik Managed"
def ready(self) -> None:
from authentik.managed.tasks import managed_reconcile
# pyright: reportGeneralTypeIssues=false
managed_reconcile() # pylint: disable=no-value-for-parameter

View File

@ -0,0 +1,56 @@
"""Managed objects manager"""
from typing import Type
from structlog.stdlib import get_logger
from authentik.managed.models import ManagedModel
LOGGER = get_logger()
class EnsureOp:
"""Ensure operation, executed as part of an ObjectManager run"""
_obj: Type[ManagedModel]
_managed_uid: str
_kwargs: dict
def __init__(self, obj: Type[ManagedModel], managed_uid: str, **kwargs) -> None:
self._obj = obj
self._managed_uid = managed_uid
self._kwargs = kwargs
def run(self):
"""Do the actual ensure action"""
raise NotImplementedError
class EnsureExists(EnsureOp):
"""Ensure object exists, with kwargs as given values"""
def run(self):
self._kwargs.setdefault("managed", self._managed_uid)
self._obj.objects.update_or_create(
**{
"managed": self._managed_uid,
"defaults": self._kwargs,
}
)
class ObjectManager:
"""Base class for Apps Object manager"""
def run(self):
"""Main entrypoint for tasks, iterate through all implementation of this
and execute all operations"""
for sub in ObjectManager.__subclasses__():
sub_inst = sub()
ops = sub_inst.reconcile()
LOGGER.debug("Reconciling managed objects", manager=sub.__name__)
for operation in ops:
operation.run()
def reconcile(self) -> list[EnsureOp]:
"""Method which is implemented in subclass that returns a list of Operations"""
raise NotImplementedError

View File

@ -0,0 +1,31 @@
"""Managed Object models"""
from django.db import models
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
class ManagedModel(models.Model):
"""Model which can be managed by authentik exclusively"""
managed = models.TextField(
default=None,
null=True,
verbose_name=_("Managed by authentik"),
help_text=_(
(
"Objects which are managed by authentik. These objects are created and updated "
"automatically. This is flag only indicates that an object can be overwritten by "
"migrations. You can still modify the objects via the API, but expect changes "
"to be overwritten in a later update."
)
),
unique=True,
)
def managed_objects(self) -> QuerySet:
"""Get all objects which are managed"""
return self.objects.exclude(managed__isnull=True)
class Meta:
abstract = True

View File

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

View File

@ -0,0 +1,20 @@
"""managed tasks"""
from django.db import DatabaseError
from authentik.core.tasks import CELERY_APP
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.managed.manager import ObjectManager
@CELERY_APP.task(bind=True, base=MonitoredTask)
def managed_reconcile(self: MonitoredTask):
"""Run ObjectManager to ensure objects are up-to-date"""
try:
ObjectManager().run()
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL, ["Successfully updated managed models."]
)
)
except DatabaseError as exc:
self.set_status(TaskResult(TaskResultStatus.WARNING, [str(exc)]))

View File

@ -363,6 +363,7 @@ class Outpost(models.Model):
intent=TokenIntents.INTENT_API,
description=f"Autogenerated by authentik for Outpost {self.name}",
expiring=False,
managed="goauthentik.io/outpost",
)
def get_required_objects(self) -> Iterable[models.Model]:

View File

@ -8,7 +8,7 @@ from django.db.models.base import Model
from django.utils.text import slugify
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)

View File

@ -1,6 +1,6 @@
"""authentik policy engine"""
from enum import Enum
from multiprocessing import Pipe, set_start_method
from multiprocessing import Pipe, current_process
from multiprocessing.connection import Connection
from typing import Iterator, List, Optional
@ -8,17 +8,14 @@ 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.stdlib import get_logger
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import User
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
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:
@ -51,6 +48,7 @@ class PolicyEngine:
use_cache: bool
request: PolicyRequest
logger: BoundLogger
mode: PolicyEngineMode
# Allow objects with no policies attached to pass
empty_result: bool
@ -64,6 +62,7 @@ class PolicyEngine:
def __init__(
self, pbm: PolicyBindingModel, user: User, request: HttpRequest = None
):
self.logger = get_logger().bind()
self.mode = PolicyEngineMode.MODE_AND
# For backwards compatibility, set empty_result to true
# objects with no policies attached will pass.
@ -107,24 +106,29 @@ class PolicyEngine:
key = cache_key(binding, self.request)
cached_policy = cache.get(key, None)
if cached_policy and self.use_cache:
LOGGER.debug(
self.logger.debug(
"P_ENG: Taking result from cache",
policy=binding.policy,
cache_key=key,
)
self.__cached_policies.append(cached_policy)
continue
LOGGER.debug("P_ENG: Evaluating policy", policy=binding.policy)
self.logger.debug("P_ENG: Evaluating policy", policy=binding.policy)
our_end, task_end = Pipe(False)
task = PolicyProcess(binding, self.request, task_end)
LOGGER.debug("P_ENG: Starting Process", policy=binding.policy)
task.start()
task.daemon = False
self.logger.debug("P_ENG: Starting Process", policy=binding.policy)
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()

View File

@ -0,0 +1,46 @@
# Generated by Django 3.1.6 on 2021-02-02 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0004_auto_20210112_2158"),
]
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"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task 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.",
),
),
]

View File

@ -0,0 +1,73 @@
# Generated by Django 3.1.6 on 2021-02-03 11:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0005_auto_20210202_1821"),
]
operations = [
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.managed", "authentik Managed"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -1,5 +1,5 @@
"""authentik policy task"""
from multiprocessing import Process
from multiprocessing import get_context
from multiprocessing.connection import Connection
from traceback import format_tb
from typing import Optional
@ -17,6 +17,9 @@ from authentik.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
TRACEBACK_HEADER = "Traceback (most recent call last):\n"
FORK_CTX = get_context("fork")
PROCESS_CLASS = FORK_CTX.Process
def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:
"""Generate Cache key for policy"""
@ -28,7 +31,7 @@ def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:
return prefix
class PolicyProcess(Process):
class PolicyProcess(PROCESS_CLASS):
"""Evaluate a single policy within a seprate process"""
connection: Connection
@ -99,17 +102,16 @@ class PolicyProcess(Process):
# Invert result if policy.negate is set
if self.binding.negate:
policy_result.passing = not policy_result.passing
key = cache_key(self.binding, self.request)
cache.set(key, policy_result)
LOGGER.debug(
"P_ENG(proc): Finished",
"P_ENG(proc): finished and cached ",
policy=self.binding.policy,
result=policy_result,
process="PolicyProcess",
passing=policy_result.passing,
user=self.request.user,
)
key = cache_key(self.binding, self.request)
cache.set(key, policy_result)
LOGGER.debug("P_ENG(proc): Cached policy evaluation", key=key)
return policy_result
def run(self): # pragma: no cover

View File

@ -3,7 +3,7 @@ from django.core.cache import cache
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.policies.reputation.models import IPReputation, UserReputation
from authentik.policies.reputation.signals import (
CACHE_KEY_IP_PREFIX,

View File

@ -26,5 +26,5 @@ def invalidate_policy_cache(sender, instance, **_):
cache.delete_many(keys)
LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
# Also delete user application cache
keys = user_app_cache_key("*")
keys = cache.keys(user_app_cache_key("*"))
cache.delete_many(keys)

View File

@ -1,20 +1,27 @@
"""OAuth2Provider API Views"""
from rest_framework.serializers import ModelSerializer
from django.shortcuts import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.generics import get_object_or_404
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.models import Provider
from authentik.providers.oauth2.models import OAuth2Provider, ScopeMapping
class OAuth2ProviderSerializer(ModelSerializer, MetaNameSerializer):
class OAuth2ProviderSerializer(ProviderSerializer):
"""OAuth2Provider Serializer"""
class Meta:
model = OAuth2Provider
fields = [
"pk",
"name",
fields = ProviderSerializer.Meta.fields + [
"authorization_flow",
"client_type",
"client_id",
@ -27,25 +34,83 @@ class OAuth2ProviderSerializer(ModelSerializer, MetaNameSerializer):
"sub_mode",
"property_mappings",
"issuer_mode",
"verbose_name",
"verbose_name_plural",
]
class OAuth2ProviderSetupURLs(Serializer):
"""OAuth2 Provider Metadata serializer"""
issuer = ReadOnlyField()
authorize = ReadOnlyField()
token = ReadOnlyField()
user_info = ReadOnlyField()
provider_info = ReadOnlyField()
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class OAuth2ProviderViewSet(ModelViewSet):
"""OAuth2Provider Viewset"""
queryset = OAuth2Provider.objects.all()
serializer_class = OAuth2ProviderSerializer
@action(methods=["GET"], detail=True)
@swagger_auto_schema(responses={200: OAuth2ProviderSetupURLs(many=False)})
# pylint: disable=invalid-name
def setup_urls(self, request: Request, pk: int) -> str:
"""Return metadata as XML string"""
provider = get_object_or_404(OAuth2Provider, pk=pk)
data = {
"issuer": provider.get_issuer(request),
"authorize": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:authorize",
)
),
"token": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:token",
)
),
"user_info": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:userinfo",
)
),
"provider_info": None,
}
try:
data["provider_info"] = request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:provider-info",
kwargs={"application_slug": provider.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
pass
return Response(data)
class ScopeMappingSerializer(ModelSerializer):
class ScopeMappingSerializer(ModelSerializer, MetaNameSerializer):
"""ScopeMapping Serializer"""
class Meta:
model = ScopeMapping
fields = ["pk", "name", "scope_name", "description", "expression"]
fields = [
"pk",
"name",
"scope_name",
"description",
"expression",
"verbose_name",
"verbose_name_plural",
]
class ScopeMappingViewSet(ModelViewSet):

View File

@ -1,4 +1,6 @@
"""authentik auth oauth provider app config"""
from importlib import import_module
from django.apps import AppConfig
@ -12,3 +14,6 @@ class AuthentikProviderOAuth2Config(AppConfig):
"authentik.providers.oauth2.urls": "application/o/",
"authentik.providers.oauth2.urls_github": "",
}
def ready(self) -> None:
import_module("authentik.providers.oauth2.managed")

View File

@ -23,11 +23,12 @@ class OAuth2Error(SentryIgnoredException):
def __repr__(self) -> str:
return self.error
def to_event(self, message: Optional[str] = None) -> Event:
def to_event(self, message: Optional[str] = None, **kwargs) -> Event:
"""Create configuration_error Event and save it."""
return Event.new(
EventAction.CONFIGURATION_ERROR,
message=message or self.description,
**kwargs,
)
@ -49,10 +50,11 @@ class RedirectUriError(OAuth2Error):
self.provided_uri = provided_uri
self.allowed_uris = allowed_uris
def to_event(self) -> Event:
def to_event(self, **kwargs) -> Event:
return super().to_event(
f"Invalid redirect URI was used. Client used '{self.provided_uri}'. "
f"Allowed redirect URIs are {','.join(self.allowed_uris)}"
f"Allowed redirect URIs are {','.join(self.allowed_uris)}",
**kwargs,
)
@ -68,8 +70,10 @@ class ClientIdError(OAuth2Error):
super().__init__()
self.client_id = client_id
def to_event(self) -> Event:
return super().to_event(f"Invalid client identifier: {self.client_id}.")
def to_event(self, **kwargs) -> Event:
return super().to_event(
f"Invalid client identifier: {self.client_id}.", **kwargs
)
class UserAuthError(OAuth2Error):

View File

@ -0,0 +1,58 @@
"""OAuth2 Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.providers.oauth2.models import ScopeMapping
SCOPE_OPENID_EXPRESSION = """
# This scope is required by the OpenID-spec, and must as such exist in authentik.
# The scope by itself does not grant any information
return {}
"""
SCOPE_EMAIL_EXPRESSION = """
return {
"email": user.email,
"email_verified": True
}
"""
SCOPE_PROFILE_EXPRESSION = """
return {
# Because authentik only saves the user's full name, and has no concept of first and last names,
# the full name is used as given name.
# You can override this behaviour in custom mappings, i.e. `user.name.split(" ")`
"name": user.name,
"given_name": user.name,
"family_name": "",
"preferred_username": user.username,
"nickname": user.username,
}
"""
class ScopeMappingManager(ObjectManager):
"""OAuth2 Provider managed objects"""
def reconcile(self):
return [
EnsureExists(
ScopeMapping,
"goauthentik.io/providers/oauth2/scope-openid",
name="authentik default OAuth Mapping: OpenID 'openid'",
scope_name="openid",
expression=SCOPE_OPENID_EXPRESSION,
),
EnsureExists(
ScopeMapping,
"goauthentik.io/providers/oauth2/scope-email",
name="authentik default OAuth Mapping: OpenID 'email'",
scope_name="email",
description="Email address",
expression=SCOPE_EMAIL_EXPRESSION,
),
EnsureExists(
ScopeMapping,
"goauthentik.io/providers/oauth2/scope-profile",
name="authentik default OAuth Mapping: OpenID 'profile'",
scope_name="profile",
description="General Profile Information",
expression=SCOPE_PROFILE_EXPRESSION,
),
]

View File

@ -10,54 +10,6 @@ import authentik.core.models
import authentik.lib.utils.time
import authentik.providers.oauth2.generators
SCOPE_OPENID_EXPRESSION = """# This is only required for OpenID Applications, but does not grant any information by itself.
return {}
"""
SCOPE_EMAIL_EXPRESSION = """return {
"email": user.email,
"email_verified": True
}
"""
SCOPE_PROFILE_EXPRESSION = """return {
"name": user.name,
"given_name": user.name,
"family_name": "",
"preferred_username": user.username,
"nickname": user.username,
}
"""
def create_default_scopes(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
ScopeMapping.objects.update_or_create(
scope_name="openid",
defaults={
"name": "Autogenerated OAuth2 Mapping: OpenID 'openid'",
"scope_name": "openid",
"description": "",
"expression": SCOPE_OPENID_EXPRESSION,
},
)
ScopeMapping.objects.update_or_create(
scope_name="email",
defaults={
"name": "Autogenerated OAuth2 Mapping: OpenID 'email'",
"scope_name": "email",
"description": "Email address",
"expression": SCOPE_EMAIL_EXPRESSION,
},
)
ScopeMapping.objects.update_or_create(
scope_name="profile",
defaults={
"name": "Autogenerated OAuth2 Mapping: OpenID 'profile'",
"scope_name": "profile",
"description": "General Profile Information",
"expression": SCOPE_PROFILE_EXPRESSION,
},
)
class Migration(migrations.Migration):
@ -235,7 +187,6 @@ class Migration(migrations.Migration):
},
bases=("authentik_core.propertymapping",),
),
migrations.RunPython(create_default_scopes),
migrations.CreateModel(
name="RefreshToken",
fields=[

View File

@ -0,0 +1,33 @@
# Generated by Django 3.1.6 on 2021-02-03 09:24
from django.apps.registry import Apps
from django.db import migrations
scope_uid_map = {
"openid": "goauthentik.io/providers/oauth2/scope-openid",
"email": "goauthentik.io/providers/oauth2/scope-email",
"profile": "goauthentik.io/providers/oauth2/scope-profile",
"ak_proxy": "goauthentik.io/providers/proxy/scope-proxy",
}
def set_managed_flag(apps: Apps, schema_editor):
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
db_alias = schema_editor.connection.alias
for mapping in ScopeMapping.objects.using(db_alias).filter(
name__startswith="Autogenerated "
):
mapping.managed = scope_uid_map[mapping.scope_name]
mapping.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0017_managed"),
("authentik_providers_oauth2", "0010_auto_20201227_1804"),
]
operations = [
migrations.RunPython(set_managed_flag),
]

View File

@ -14,7 +14,6 @@ from django.conf import settings
from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest
from django.shortcuts import reverse
from django.utils import dateformat, timezone
from django.utils.translation import gettext_lazy as _
from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key
@ -25,7 +24,6 @@ from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.lib.utils.template import render_to_string
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
from authentik.providers.oauth2.apps import AuthentikProviderOAuth2Config
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
@ -118,6 +116,12 @@ class ScopeMapping(PropertyMapping):
return ScopeMappingForm
@property
def serializer(self) -> Type[Serializer]:
from authentik.providers.oauth2.api import ScopeMappingSerializer
return ScopeMappingSerializer
def __str__(self):
return f"Scope Mapping {self.name} ({self.scope_name})"
@ -303,41 +307,6 @@ class OAuth2Provider(Provider):
jws = JWS(payload, alg=self.jwt_alg)
return jws.sign_compact(keys)
def html_setup_urls(self, request: HttpRequest) -> Optional[str]:
"""return template and context modal with URLs for authorize, token, openid-config, etc"""
try:
# pylint: disable=no-member
return render_to_string(
"providers/oauth2/setup_url_modal.html",
{
"provider": self,
"issuer": self.get_issuer(request),
"authorize": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:authorize",
)
),
"token": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:token",
)
),
"userinfo": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:userinfo",
)
),
"provider_info": request.build_absolute_uri(
reverse(
"authentik_providers_oauth2:provider-info",
kwargs={"application_slug": self.application.slug},
)
),
},
)
except Provider.application.RelatedObjectDoesNotExist:
return None
class Meta:
verbose_name = _("OAuth2/OpenID Provider")

View File

@ -1,50 +0,0 @@
{% load i18n %}
<ak-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'View Setup URLs' %}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form class="pf-c-form">
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">{% trans 'OpenID Configuration URL' %}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="{{ provider_info }}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">{% trans 'OpenID Configuration Issuer' %}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="{{ issuer }}" />
</div>
<hr>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">{% trans 'Authorize URL' %}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="{{ authorize }}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">{% trans 'Token URL' %}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="{{ token }}" />
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">{% trans 'Userinfo Endpoint' %}</span>
</label>
<input class="pf-c-form-control" readonly type="text" value="{{ userinfo }}" />
</div>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
</footer>
</div>
</ak-modal-button>

View File

@ -256,12 +256,12 @@ class OAuthFulfillmentStage(StageView):
).from_http(self.request)
return redirect(self.create_response_uri())
except (ClientIdError, RedirectUriError) as error:
error.to_event().from_http(request)
error.to_event(application=application).from_http(request)
self.executor.stage_invalid()
# pylint: disable=no-member
return bad_request_message(request, error.description, title=error.error)
except AuthorizeError as error:
error.to_event().from_http(request)
error.to_event(application=application).from_http(request)
self.executor.stage_invalid()
return redirect(error.create_uri())
@ -379,7 +379,7 @@ class AuthorizationFlowInitView(PolicyAccessView):
try:
self.params = OAuthAuthorizationParams.from_request(self.request)
except AuthorizeError as error:
error.to_event().from_http(self.request)
error.to_event(redirect_uri=error.redirect_uri).from_http(self.request)
raise RequestValidationError(redirect(error.create_uri()))
except OAuth2Error as error:
error.to_event().from_http(self.request)
@ -396,7 +396,7 @@ class AuthorizationFlowInitView(PolicyAccessView):
self.params.grant_type,
self.params.state,
)
error.to_event().from_http(self.request)
error.to_event(redirect_uri=error.redirect_uri).from_http(self.request)
raise RequestValidationError(redirect(error.create_uri()))
def resolve_provider_application(self):

View File

@ -6,7 +6,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.providers import ProviderSerializer
from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyProvider
@ -34,7 +34,7 @@ class OpenIDConnectConfigurationSerializer(Serializer):
raise NotImplementedError
class ProxyProviderSerializer(MetaNameSerializer, ModelSerializer):
class ProxyProviderSerializer(ProviderSerializer):
"""ProxyProvider Serializer"""
def create(self, validated_data):
@ -50,9 +50,7 @@ class ProxyProviderSerializer(MetaNameSerializer, ModelSerializer):
class Meta:
model = ProxyProvider
fields = [
"pk",
"name",
fields = ProviderSerializer.Meta.fields + [
"internal_host",
"external_host",
"internal_host_ssl_validation",
@ -61,8 +59,6 @@ class ProxyProviderSerializer(MetaNameSerializer, ModelSerializer):
"basic_auth_enabled",
"basic_auth_password_attribute",
"basic_auth_user_attribute",
"verbose_name",
"verbose_name_plural",
]

View File

@ -1,4 +1,6 @@
"""authentik Proxy app"""
from importlib import import_module
from django.apps import AppConfig
@ -8,3 +10,6 @@ class AuthentikProviderProxyConfig(AppConfig):
name = "authentik.providers.proxy"
label = "authentik_providers_proxy"
verbose_name = "authentik Providers.Proxy"
def ready(self) -> None:
import_module("authentik.providers.proxy.managed")

View File

@ -0,0 +1,28 @@
"""OAuth2 Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.providers.oauth2.models import ScopeMapping
from authentik.providers.proxy.models import SCOPE_AK_PROXY
SCOPE_AK_PROXY_EXPRESSION = """
# This mapping is used by the authentik proxy. It passes extra user attributes,
# which are used for example for the HTTP-Basic Authentication mapping.
return {
"ak_proxy": {
"user_attributes": user.group_attributes()
}
}"""
class ProxyScopeMappingManager(ObjectManager):
"""OAuth2 Provider managed objects"""
def reconcile(self):
return [
EnsureExists(
ScopeMapping,
"goauthentik.io/providers/proxy/scope-proxy",
name="authentik default OAuth Mapping: proxy outpost",
scope_name=SCOPE_AK_PROXY,
expression=SCOPE_AK_PROXY_EXPRESSION,
),
]

View File

@ -1,35 +1,5 @@
# Generated by Django 3.1.4 on 2020-12-14 09:42
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
SCOPE_AK_PROXY_EXPRESSION = """return {
"ak_proxy": {
"user_attributes": user.group_attributes()
}
}"""
def create_proxy_scope(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.providers.proxy.models import SCOPE_AK_PROXY, ProxyProvider
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
ScopeMapping.objects.filter(scope_name="pb_proxy").delete()
ScopeMapping.objects.update_or_create(
scope_name=SCOPE_AK_PROXY,
defaults={
"name": "Autogenerated OAuth2 Mapping: authentik Proxy",
"scope_name": SCOPE_AK_PROXY,
"description": "",
"expression": SCOPE_AK_PROXY_EXPRESSION,
},
)
for provider in ProxyProvider.objects.all():
provider.set_oauth_defaults()
provider.save()
class Migration(migrations.Migration):
@ -38,4 +8,4 @@ class Migration(migrations.Migration):
("authentik_providers_proxy", "0009_auto_20201007_1721"),
]
operations = [migrations.RunPython(create_proxy_scope)]
operations = []

View File

@ -6,7 +6,6 @@ from urllib.parse import urljoin
from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest
from django.utils.translation import gettext as _
from rest_framework.serializers import Serializer
@ -119,10 +118,6 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
"""Use external_host as launch URL"""
return self.external_host
def html_setup_urls(self, request: HttpRequest) -> Optional[str]:
"""Overwrite Setup URLs as they are not needed for proxy"""
return None
def set_oauth_defaults(self):
"""Ensure all OAuth2-related settings are correct"""
self.client_type = ClientTypes.CONFIDENTIAL

View File

@ -1,20 +1,26 @@
"""SAMLProvider API Views"""
from rest_framework.serializers import ModelSerializer
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.generics import get_object_or_404
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.utils import MetaNameSerializer
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.views import DescriptorDownloadView
class SAMLProviderSerializer(ModelSerializer, MetaNameSerializer):
class SAMLProviderSerializer(ProviderSerializer):
"""SAMLProvider Serializer"""
class Meta:
model = SAMLProvider
fields = [
"pk",
"name",
fields = ProviderSerializer.Meta.fields + [
"acs_url",
"audience",
"issuer",
@ -22,29 +28,57 @@ class SAMLProviderSerializer(ModelSerializer, MetaNameSerializer):
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
"property_mappings",
"name_id_mapping",
"digest_algorithm",
"signature_algorithm",
"signing_kp",
"verification_kp",
"verbose_name",
"verbose_name_plural",
]
class SAMLMetadataSerializer(Serializer):
"""SAML Provider Metadata serializer"""
metadata = ReadOnlyField()
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class SAMLProviderViewSet(ModelViewSet):
"""SAMLProvider Viewset"""
queryset = SAMLProvider.objects.all()
serializer_class = SAMLProviderSerializer
@action(methods=["GET"], detail=True)
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)})
# pylint: disable=invalid-name
def metadata(self, request: Request, pk: int) -> str:
"""Return metadata as XML string"""
provider = get_object_or_404(SAMLProvider, pk=pk)
metadata = DescriptorDownloadView.get_metadata(request, provider)
return Response({"metadata": metadata})
class SAMLPropertyMappingSerializer(ModelSerializer):
class SAMLPropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
"""SAMLPropertyMapping Serializer"""
class Meta:
model = SAMLPropertyMapping
fields = ["pk", "name", "saml_name", "friendly_name", "expression"]
fields = [
"pk",
"name",
"saml_name",
"friendly_name",
"expression",
"verbose_name",
"verbose_name_plural",
]
class SAMLPropertyMappingViewSet(ModelViewSet):

View File

@ -1,4 +1,5 @@
"""authentik SAML IdP app config"""
from importlib import import_module
from django.apps import AppConfig
@ -10,3 +11,6 @@ class AuthentikProviderSAMLConfig(AppConfig):
label = "authentik_providers_saml"
verbose_name = "authentik Providers.SAML"
mountpoint = "application/saml/"
def ready(self) -> None:
import_module("authentik.providers.saml.managed")

View File

@ -42,6 +42,7 @@ class SAMLProviderForm(forms.ModelForm):
"signing_kp",
"verification_kp",
"property_mappings",
"name_id_mapping",
"assertion_valid_not_before",
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
@ -84,7 +85,9 @@ class SAMLPropertyMappingForm(forms.ModelForm):
"saml_name": mark_safe(
_(
"URN OID used by SAML. This is optional. "
'<a href="https://www.rfc-editor.org/rfc/rfc2798.html#section-2">Reference</a>'
'<a href="https://www.rfc-editor.org/rfc/rfc2798.html#section-2">Reference</a>.'
" If this property mapping is used for NameID Property, "
"this field is discarded."
)
),
}

View File

@ -0,0 +1,67 @@
"""SAML Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping
GROUP_EXPRESSION = """
for group in user.ak_groups.all():
yield group.name
"""
class SAMLProviderManager(ObjectManager):
"""SAML Provider managed objects"""
def reconcile(self):
return [
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/upn",
name="authentik default SAML Mapping: UPN",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
expression="return user.attributes.get('upn', user.email)",
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/name",
name="authentik default SAML Mapping: Name",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
expression="return user.name",
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/email",
name="authentik default SAML Mapping: Email",
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
expression="return user.email",
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/username",
name="authentik default SAML Mapping: Username",
saml_name="http://schemas.goauthentik.io/2021/02/saml/username",
expression="return user.username",
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/uid",
name="authentik default SAML Mapping: User ID",
saml_name="http://schemas.goauthentik.io/2021/02/saml/uid",
expression="return user.pk",
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/groups",
name="authentik default SAML Mapping: Groups",
saml_name="http://schemas.xmlsoap.org/claims/Group",
expression=GROUP_EXPRESSION,
),
EnsureExists(
SAMLPropertyMapping,
"goauthentik.io/providers/saml/ms-windowsaccountname",
name="authentik default SAML Mapping: WindowsAccountname (Username)",
saml_name=(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
),
expression="return user.username",
),
]

View File

@ -3,61 +3,10 @@
from django.db import migrations
def create_default_property_mappings(apps, schema_editor):
"""Create default SAML Property Mappings"""
SAMLPropertyMapping = apps.get_model(
"authentik_providers_saml", "SAMLPropertyMapping"
)
db_alias = schema_editor.connection.alias
defaults = [
{
"FriendlyName": "eduPersonPrincipalName",
"Name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
"Expression": "return user.email",
},
{
"FriendlyName": "cn",
"Name": "urn:oid:2.5.4.3",
"Expression": "return user.name",
},
{
"FriendlyName": "mail",
"Name": "urn:oid:0.9.2342.19200300.100.1.3",
"Expression": "return user.email",
},
{
"FriendlyName": "displayName",
"Name": "urn:oid:2.16.840.1.113730.3.1.241",
"Expression": "return user.username",
},
{
"FriendlyName": "uid",
"Name": "urn:oid:0.9.2342.19200300.100.1.1",
"Expression": "return user.pk",
},
{
"FriendlyName": "member-of",
"Name": "member-of",
"Expression": "for group in user.groups.all():\n yield group.name",
},
]
for default in defaults:
SAMLPropertyMapping.objects.using(db_alias).get_or_create(
saml_name=default["Name"],
friendly_name=default["FriendlyName"],
expression=default["Expression"],
defaults={
"name": f"Autogenerated SAML Mapping: {default['FriendlyName']} -> {default['Expression']}"
},
)
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0001_initial"),
]
operations = [
migrations.RunPython(create_default_property_mappings),
]
operations = []

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.4 on 2021-01-28 21:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0010_auto_20201230_2112"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="name_id_mapping",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be considered",
null=True,
verbose_name="NameID Property Mapping",
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_providers_saml.samlpropertymapping",
),
),
]

View File

@ -0,0 +1,57 @@
# Generated by Django 3.1.6 on 2021-02-02 19:21
from django.db import migrations
saml_name_map = {
"http://schemas.xmlsoap.org/claims/CommonName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname",
"member-of": "http://schemas.xmlsoap.org/claims/Group",
"http://schemas.xmlsoap.org/claims/Group": "http://schemas.xmlsoap.org/claims/Group",
"urn:oid:0.9.2342.19200300.100.1.1": "http://schemas.goauthentik.io/2021/02/saml/uid",
"urn:oid:0.9.2342.19200300.100.1.3": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"urn:oid:1.3.6.1.4.1.5923.1.1.1.6": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
"urn:oid:2.16.840.1.113730.3.1.241": "http://schemas.goauthentik.io/2021/02/saml/username",
"urn:oid:2.5.4.3": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
}
saml_name_uid_map = {
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn": "goauthentik.io/providers/saml/upn",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "goauthentik.io/providers/saml/name",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "goauthentik.io/providers/saml/email",
"http://schemas.goauthentik.io/2021/02/saml/username": "goauthentik.io/providers/saml/username",
"http://schemas.goauthentik.io/2021/02/saml/uid": "goauthentik.io/providers/saml/uid",
"http://schemas.xmlsoap.org/claims/Group": "goauthentik.io/providers/saml/groups",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname": "goauthentik.io/providers/saml/ms-windowsaccountname",
}
def add_managed_update(apps, schema_editor):
"""Create default SAML Property Mappings"""
SAMLPropertyMapping = apps.get_model(
"authentik_providers_saml", "SAMLPropertyMapping"
)
db_alias = schema_editor.connection.alias
for pm in SAMLPropertyMapping.objects.using(db_alias).filter(
name__startswith="Autogenerated "
):
if pm.saml_name not in saml_name_map:
continue
new_name = saml_name_map[pm.saml_name]
if not new_name:
pm.delete()
continue
pm.saml_name = new_name
pm.managed = saml_name_uid_map[new_name]
pm.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0017_managed"),
("authentik_providers_saml", "0011_samlprovider_name_id_mapping"),
]
operations = [
migrations.RunPython(add_managed_update),
]

View File

@ -4,15 +4,12 @@ from urllib.parse import urlparse
from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest
from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.utils.template import render_to_string
from authentik.lib.utils.time import timedelta_string_validator
from authentik.sources.saml.processors.constants import (
DSA_SHA1,
@ -65,6 +62,21 @@ class SAMLProvider(Provider):
),
)
name_id_mapping = models.ForeignKey(
"SAMLPropertyMapping",
default=None,
blank=True,
null=True,
on_delete=models.SET_DEFAULT,
verbose_name=_("NameID Property Mapping"),
help_text=_(
(
"Configure how the NameID value will be created. When left empty, "
"the NameIDPolicy of the incoming request will be considered"
)
),
)
assertion_valid_not_before = models.TextField(
default="minutes=-5",
validators=[timedelta_string_validator],
@ -167,31 +179,6 @@ class SAMLProvider(Provider):
def __str__(self):
return f"SAML Provider {self.name}"
def link_download_metadata(self):
"""Get link to download XML metadata for admin interface"""
try:
# pylint: disable=no-member
return reverse(
"authentik_providers_saml:metadata",
kwargs={"application_slug": self.application.slug},
)
except Provider.application.RelatedObjectDoesNotExist:
return None
def html_metadata_view(self, request: HttpRequest) -> Optional[str]:
"""return template and context modal to view Metadata without downloading it"""
from authentik.providers.saml.views import DescriptorDownloadView
try:
# pylint: disable=no-member
metadata = DescriptorDownloadView.get_metadata(request, self)
return render_to_string(
"providers/saml/admin_metadata_modal.html",
{"provider": self, "metadata": metadata},
)
except Provider.application.RelatedObjectDoesNotExist:
return None
class Meta:
verbose_name = _("SAML Provider")
@ -210,6 +197,12 @@ class SAMLPropertyMapping(PropertyMapping):
return SAMLPropertyMappingForm
@property
def serializer(self) -> Type[Serializer]:
from authentik.providers.saml.api import SAMLPropertyMappingSerializer
return SAMLPropertyMappingSerializer
def __str__(self):
name = self.friendly_name if self.friendly_name != "" else self.saml_name
return f"{self.name} ({name})"

View File

@ -3,6 +3,7 @@ from hashlib import sha256
from types import GeneratorType
import xmlsec
from django.conf import settings
from django.http import HttpRequest
from lxml import etree # nosec
from lxml.etree import Element, SubElement # nosec
@ -14,6 +15,7 @@ from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.request_parser import AuthNRequest
from authentik.providers.saml.utils import get_random_id
from authentik.providers.saml.utils.time import get_time_string
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
from authentik.sources.saml.exceptions import UnsupportedNameIDFormat
from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
@ -23,6 +25,7 @@ from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
SIGN_ALGORITHM_TRANSFORM_MAP,
)
@ -78,7 +81,8 @@ class AssertionProcessor:
continue
attribute = Element(f"{{{NS_SAML_ASSERTION}}}Attribute")
attribute.attrib["FriendlyName"] = mapping.friendly_name
if mapping.friendly_name and mapping.friendly_name != "":
attribute.attrib["FriendlyName"] = mapping.friendly_name
attribute.attrib["Name"] = mapping.saml_name
if not isinstance(value, (list, GeneratorType)):
@ -137,24 +141,48 @@ class AssertionProcessor:
audience.text = self.provider.audience
return conditions
# pylint: disable=too-many-return-statements
def get_name_id(self) -> Element:
"""Get NameID Element"""
name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID")
name_id.attrib["Format"] = self.auth_n_request.name_id_policy
# persistent is used as a fallback, so always generate it
persistent = sha256(
f"{self.http_request.user.id}-{settings.SECRET_KEY}".encode("ascii")
).hexdigest()
name_id.text = persistent
# If name_id_mapping is set, we override the value, regardless of what the SP asks for
if self.provider.name_id_mapping:
try:
value = self.provider.name_id_mapping.evaluate(
user=self.http_request.user,
request=self.http_request,
provider=self.provider,
)
if value is not None:
name_id.text = value
return name_id
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL:
name_id.text = self.http_request.user.email
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_PERSISTENT:
name_id.text = self.http_request.user.username
name_id.text = persistent
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_X509:
# This attribute is statically set by the LDAP source
name_id.text = self.http_request.user.attributes.get(
"distinguishedName", ""
LDAP_DISTINGUISHED_NAME, persistent
)
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_WINDOWS:
# This attribute is statically set by the LDAP source
name_id.text = self.http_request.user.attributes.get("upn", persistent)
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
# Use the hash of the user's session, which changes every session
session_key: str = self.http_request.user.session.session_key
name_id.text = sha256(session_key.encode()).hexdigest()
return name_id

View File

@ -10,7 +10,12 @@ from lxml import etree # nosec
from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
from authentik.flows.models import Flow
from authentik.providers.saml.models import (
SAMLBindings,
SAMLPropertyMapping,
SAMLProvider,
)
from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER
from authentik.sources.saml.processors.constants import (
NS_MAP,
@ -48,10 +53,13 @@ class ServiceProviderMetadata:
signing_keypair: Optional[CertificateKeyPair] = None
def to_provider(self, name: str) -> SAMLProvider:
def to_provider(self, name: str, authorization_flow: Flow) -> SAMLProvider:
"""Create a SAMLProvider instance from the details. `name` is required,
as depending on the metadata CertificateKeypairs might have to be created."""
provider = SAMLProvider(name=name)
provider = SAMLProvider.objects.create(
name=name,
authorization_flow=authorization_flow,
)
provider.issuer = self.entity_id
provider.sp_binding = self.acs_binding
provider.acs_url = self.acs_location
@ -63,6 +71,12 @@ class ServiceProviderMetadata:
provider.signing_kp = CertificateKeyPair.objects.exclude(
key_data__iexact=""
).first()
# Set all auto-generated Property-mappings as defaults
# They should provide a sane default for most applications:
provider.property_mappings.set(
SAMLPropertyMapping.objects.filter(name__startswith="Autogenerated")
)
provider.save()
return provider

View File

@ -1,22 +0,0 @@
{% load i18n %}
<ak-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'View Metadata' %}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form method="post">
<ak-codemirror mode="xml"><textarea class="pf-c-form-control"
readonly>{{ metadata }}</textarea></ak-codemirror>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
</footer>
</div>
</ak-modal-button>

View File

@ -3,7 +3,8 @@
from django.test import TestCase
from authentik.providers.saml.models import SAMLBindings
from authentik.flows.models import Flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping
from authentik.providers.saml.processors.metadata_parser import (
ServiceProviderMetadataParser,
)
@ -65,18 +66,25 @@ bHlUY7ytSUTowXA=
class TestServiceProviderMetadataParser(TestCase):
"""Test ServiceProviderMetadataParser parsing and creation of SAML Provider"""
def setUp(self) -> None:
self.flow = Flow.objects.first()
def test_simple(self):
"""Test simple metadata without Singing"""
metadata = ServiceProviderMetadataParser().parse(METADATA_SIMPLE)
provider = metadata.to_provider("test")
provider = metadata.to_provider("test", self.flow)
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
self.assertEqual(
len(provider.property_mappings.all()),
len(SAMLPropertyMapping.objects.filter(name__startswith="Autogenerated")),
)
def test_with_signing_cert(self):
"""Test Metadata with signing cert"""
metadata = ServiceProviderMetadataParser().parse(METADATA_CERT)
provider = metadata.to_provider("test")
provider = metadata.to_provider("test", self.flow)
self.assertEqual(
provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs"
)

View File

@ -8,6 +8,7 @@ from lxml import etree # nosec
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
@ -20,6 +21,7 @@ class TestSchema(TestCase):
"""Test Requests and Responses against schema"""
def setUp(self):
ObjectManager().run()
cert = CertificateKeyPair.objects.first()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=Flow.objects.get(

View File

@ -269,9 +269,10 @@ class MetadataImportView(LoginRequiredMixin, FormView):
metadata = ServiceProviderMetadataParser().parse(
form.cleaned_data["metadata"].read().decode()
)
provider = metadata.to_provider(form.cleaned_data["provider_name"])
provider.authorization_flow = form.cleaned_data["authorization_flow"]
provider.save()
metadata.to_provider(
form.cleaned_data["provider_name"],
form.cleaned_data["authorization_flow"],
)
messages.success(self.request, _("Successfully created Provider"))
except ValueError as exc:
LOGGER.warning(str(exc))

View File

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import importlib
import logging
import os
import sys
from json import dumps
@ -26,7 +27,7 @@ from sentry_sdk.integrations.redis import RedisIntegration
from authentik import __version__
from authentik.core.middleware import structlog_add_request_id
from authentik.lib.config import CONFIG
from authentik.lib.logging import add_common_fields, add_process_id
from authentik.lib.logging import add_process_id
from authentik.lib.sentry import before_send
@ -129,6 +130,7 @@ INSTALLED_APPS = [
"django_prometheus",
"channels",
"dbbackup",
"authentik.managed.apps.AuthentikManagedConfig",
]
GUARDIAN_MONKEY_PATCH = False
@ -336,7 +338,7 @@ if not DEBUG and _ERROR_REPORTING:
RedisIntegration(),
],
before_send=before_send,
release="authentik@%s" % __version__,
release=f"authentik@{__version__}",
traces_sample_rate=0.6,
environment=CONFIG.y("error_reporting.environment", "customer"),
send_default_pii=CONFIG.y_bool("error_reporting.send_pii", False),
@ -353,23 +355,26 @@ if not DEBUG and _ERROR_REPORTING:
STATIC_URL = "/static/"
MEDIA_URL = "/media/"
LOG_LEVEL = CONFIG.y("log_level").upper()
structlog.configure_once(
processors=[
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.threadlocal.merge_threadlocal_context,
add_process_id,
add_common_fields(CONFIG.y("error_reporting.environment", "customer")),
structlog_add_request_id,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(),
structlog.processors.TimeStamper(fmt="iso", utc=False),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=structlog.threadlocal.wrap_dict(dict),
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
wrapper_class=structlog.make_filtering_bound_logger(
getattr(logging, LOG_LEVEL, logging.WARNING)
),
cache_logger_on_first_use=True,
)
@ -410,8 +415,6 @@ LOGGING = {
TEST = False
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
LOG_LEVEL = CONFIG.y("log_level").upper()
_LOGGING_HANDLER_MAP = {
"": LOG_LEVEL,

View File

@ -13,7 +13,7 @@ class PytestTestRunner: # pragma: no cover
self.keepdb = keepdb
settings.TEST = True
settings.CELERY_TASK_ALWAYS_EAGER = True
CONFIG.raw.get("authentik")["avatars"] = "none"
CONFIG.y_set("authentik.avatars", "none")
def run_tests(self, test_labels):
"""Run pytest and return the exitcode.

View File

@ -50,7 +50,7 @@ for _authentik_app in get_apps():
LOGGER.debug(
"Mounted URLs",
app_name=_authentik_app.name,
mountpoint=mountpoint,
app_mountpoint=mountpoint,
namespace=namespace,
)

View File

@ -22,23 +22,31 @@ class LDAPSourceSerializer(ModelSerializer, MetaNameSerializer):
"additional_group_dn",
"user_object_filter",
"group_object_filter",
"user_group_membership_field",
"group_membership_field",
"object_uniqueness_field",
"sync_users",
"sync_users_password",
"sync_groups",
"sync_parent_group",
"property_mappings",
"property_mappings_group",
]
extra_kwargs = {"bind_password": {"write_only": True}}
class LDAPPropertyMappingSerializer(ModelSerializer):
class LDAPPropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
"""LDAP PropertyMapping Serializer"""
class Meta:
model = LDAPPropertyMapping
fields = ["pk", "name", "expression", "object_field"]
fields = [
"pk",
"name",
"expression",
"object_field",
"verbose_name",
"verbose_name_plural",
]
class LDAPSourceViewSet(ModelViewSet):

View File

@ -13,3 +13,4 @@ class AuthentikSourceLDAPConfig(AppConfig):
def ready(self):
import_module("authentik.sources.ldap.signals")
import_module("authentik.sources.ldap.managed")

View File

@ -10,6 +10,7 @@ from authentik.core.models import User
from authentik.sources.ldap.models import LDAPSource
LOGGER = get_logger()
LDAP_DISTINGUISHED_NAME = "distinguishedName"
class LDAPBackend(ModelBackend):
@ -35,7 +36,7 @@ class LDAPBackend(ModelBackend):
if not users.exists():
return None
user: User = users.first()
if "distinguishedName" not in user.attributes:
if LDAP_DISTINGUISHED_NAME not in user.attributes:
LOGGER.debug(
"User doesn't have DN set, assuming not LDAP imported.", user=user
)
@ -63,7 +64,7 @@ class LDAPBackend(ModelBackend):
try:
temp_connection = ldap3.Connection(
source.connection.server,
user=user.attributes.get("distinguishedName"),
user=user.attributes.get(LDAP_DISTINGUISHED_NAME),
password=password,
raise_exceptions=True,
)

View File

@ -14,6 +14,9 @@ class LDAPSourceForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["property_mappings"].queryset = LDAPPropertyMapping.objects.all()
self.fields[
"property_mappings_group"
].queryset = LDAPPropertyMapping.objects.all()
class Meta:
@ -33,14 +36,16 @@ class LDAPSourceForm(forms.ModelForm):
"sync_users_password",
"sync_groups",
"property_mappings",
"property_mappings_group",
"additional_user_dn",
"additional_group_dn",
"user_object_filter",
"group_object_filter",
"user_group_membership_field",
"group_membership_field",
"object_uniqueness_field",
"sync_parent_group",
]
labels = {"property_mappings_group": _("Group property mappings")}
widgets = {
"name": forms.TextInput(),
"server_uri": forms.TextInput(),
@ -51,7 +56,7 @@ class LDAPSourceForm(forms.ModelForm):
"additional_group_dn": forms.TextInput(),
"user_object_filter": forms.TextInput(),
"group_object_filter": forms.TextInput(),
"user_group_membership_field": forms.TextInput(),
"group_membership_field": forms.TextInput(),
"object_uniqueness_field": forms.TextInput(),
}

View File

@ -0,0 +1,69 @@
"""LDAP Source managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.sources.ldap.models import LDAPPropertyMapping
class LDAPProviderManager(ObjectManager):
"""LDAP Source managed objects"""
def reconcile(self):
return [
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/default-name",
name="authentik default LDAP Mapping: Name",
object_field="name",
expression="return ldap.get('name')",
),
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/default-mail",
name="authentik default LDAP Mapping: mail",
object_field="email",
expression="return ldap.get('mail')",
),
# Active Directory-specific mappings
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/ms-samaccountname",
name="authentik default Active Directory Mapping: sAMAccountName",
object_field="username",
expression="return ldap.get('sAMAccountName')",
),
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/ms-userprincipalname",
name="authentik default Active Directory Mapping: userPrincipalName",
object_field="attributes.upn",
expression="return ldap.get('userPrincipalName')",
),
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/ms-givenName",
name="authentik default Active Directory Mapping: givenName",
object_field="attributes.givenName",
expression="return ldap.get('givenName')",
),
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/ms-sn",
name="authentik default Active Directory Mapping: sn",
object_field="attributes.sn",
expression="return ldap.get('sn')",
),
# OpenLDAP specific mappings
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/openldap-uid",
name="authentik default OpenLDAP Mapping: uid",
object_field="username",
expression="return ldap.get('uid')",
),
EnsureExists(
LDAPPropertyMapping,
"goauthentik.io/sources/ldap/openldap-cn",
name="authentik default OpenLDAP Mapping: cn",
object_field="name",
expression="return ldap.get('cn')",
),
]

View File

@ -1,37 +1,12 @@
# Generated by Django 3.0.6 on 2020-05-23 19:30
from django.apps.registry import Apps
from django.db import migrations
def create_default_ad_property_mappings(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model(
"authentik_sources_ldap", "LDAPPropertyMapping"
)
mapping = {
"name": "return ldap.get('name')",
"first_name": "return ldap.get('givenName')",
"last_name": "return ldap.get('sn')",
"username": "return ldap.get('sAMAccountName')",
"email": "return ldap.get('mail')",
}
db_alias = schema_editor.connection.alias
for object_field, expression in mapping.items():
LDAPPropertyMapping.objects.using(db_alias).get_or_create(
expression=expression,
object_field=object_field,
defaults={
"name": f"Autogenerated LDAP Mapping: {expression} -> {object_field}"
},
)
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0002_ldapsource_sync_users"),
]
operations = [
migrations.RunPython(create_default_ad_property_mappings),
]
operations = []

View File

@ -1,50 +1,12 @@
# Generated by Django 3.1.1 on 2020-09-15 19:19
from django.apps.registry import Apps
from django.db import migrations
def create_default_property_mappings(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model(
"authentik_sources_ldap", "LDAPPropertyMapping"
)
db_alias = schema_editor.connection.alias
mapping = {
"name": "name",
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
for object_field, ldap_field in mapping.items():
expression = f"return ldap.get('{ldap_field}')"
LDAPPropertyMapping.objects.using(db_alias).get_or_create(
expression=expression,
object_field=object_field,
defaults={
"name": f"Autogenerated LDAP Mapping: {ldap_field} -> {object_field}"
},
)
ad_mapping = {
"username": "sAMAccountName",
"attributes.upn": "userPrincipalName",
}
for object_field, ldap_field in ad_mapping.items():
expression = f"return ldap.get('{ldap_field}')"
LDAPPropertyMapping.objects.using(db_alias).get_or_create(
expression=expression,
object_field=object_field,
defaults={
"name": f"Autogenerated Active Directory Mapping: {ldap_field} -> {object_field}"
},
)
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0005_auto_20200913_1947"),
]
operations = [
migrations.RunPython(create_default_property_mappings),
]
operations = []

View File

@ -0,0 +1,34 @@
# Generated by Django 3.1.6 on 2021-02-02 20:51
from django.apps.registry import Apps
from django.db import migrations
def set_managed_flag(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model(
"authentik_sources_ldap", "LDAPPropertyMapping"
)
db_alias = schema_editor.connection.alias
field_to_uid = {
"name": "goauthentik.io/sources/ldap/default-name",
"email": "goauthentik.io/sources/ldap/default-mail",
"username": "goauthentik.io/sources/ldap/ms-samaccountname",
"attributes.upn": "goauthentik.io/sources/ldap/ms-userprincipalname",
"first_name": "goauthentik.io/sources/ldap/ms-givenName",
"last_name": "goauthentik.io/sources/ldap/ms-sn",
}
for mapping in LDAPPropertyMapping.objects.using(db_alias).filter(
name__startswith="Autogenerated "
):
mapping.managed = field_to_uid.get(mapping.object_field)
mapping.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0017_managed"),
("authentik_sources_ldap", "0007_ldapsource_sync_users_password"),
]
operations = [migrations.RunPython(set_managed_flag)]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.6 on 2021-02-04 18:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0008_managed"),
]
operations = [
migrations.RemoveField(
model_name="ldapsource",
name="user_group_membership_field",
),
migrations.AddField(
model_name="ldapsource",
name="group_membership_field",
field=models.TextField(
default="member", help_text="Field which contains members of a group."
),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.1.6 on 2021-02-05 10:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0009_auto_20210204_1834"),
]
operations = [
migrations.AlterField(
model_name="ldapsource",
name="group_object_filter",
field=models.TextField(
default="(objectClass=group)",
help_text="Consider Objects matching this filter to be Groups.",
),
),
migrations.AlterField(
model_name="ldapsource",
name="user_object_filter",
field=models.TextField(
default="(objectClass=person)",
help_text="Consider Objects matching this filter to be Users.",
),
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 3.1.6 on 2021-02-06 14:01
from django.apps.registry import Apps
from django.db import migrations, models
def set_default_group_mappings(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model(
"authentik_sources_ldap", "LDAPPropertyMapping"
)
LDAPSource = apps.get_model("authentik_sources_ldap", "LDAPSource")
db_alias = schema_editor.connection.alias
for source in LDAPSource.objects.using(db_alias).all():
if source.property_mappings_group.exists():
continue
source.property_mappings_group.set(
LDAPPropertyMapping.objects.using(db_alias).filter(
managed="goauthentik.io/sources/ldap/default-name"
)
)
source.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0010_auto_20210205_1027"),
]
operations = [
migrations.AddField(
model_name="ldapsource",
name="property_mappings_group",
field=models.ManyToManyField(
blank=True,
default=None,
help_text="Property mappings used for group creation/updating.",
to="authentik_core.PropertyMapping",
),
),
migrations.RunPython(set_default_group_mappings),
]

View File

@ -38,20 +38,27 @@ class LDAPSource(Source):
)
user_object_filter = models.TextField(
default="(objectCategory=Person)",
default="(objectClass=person)",
help_text=_("Consider Objects matching this filter to be Users."),
)
user_group_membership_field = models.TextField(
default="memberOf", help_text=_("Field which contains Groups of user.")
group_membership_field = models.TextField(
default="member", help_text=_("Field which contains members of a group.")
)
group_object_filter = models.TextField(
default="(objectCategory=Group)",
default="(objectClass=group)",
help_text=_("Consider Objects matching this filter to be Groups."),
)
object_uniqueness_field = models.TextField(
default="objectSid", help_text=_("Field which contains a unique Identifier.")
)
property_mappings_group = models.ManyToManyField(
PropertyMapping,
default=None,
blank=True,
help_text=_("Property mappings used for group creation/updating."),
)
sync_users = models.BooleanField(default=True)
sync_users_password = models.BooleanField(
default=True,
@ -130,6 +137,12 @@ class LDAPPropertyMapping(PropertyMapping):
return LDAPPropertyMappingForm
@property
def serializer(self) -> Type[Serializer]:
from authentik.sources.ldap.api import LDAPPropertyMappingSerializer
return LDAPPropertyMappingSerializer
def __str__(self):
return self.name

View File

@ -8,6 +8,7 @@ import ldap3.core.exceptions
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
from authentik.sources.ldap.models import LDAPSource
LOGGER = get_logger()
@ -74,9 +75,9 @@ class LDAPPasswordChanger:
def change_password(self, user: User, password: str):
"""Change user's password"""
user_dn = user.attributes.get("distinguishedName", None)
user_dn = user.attributes.get(LDAP_DISTINGUISHED_NAME, None)
if not user_dn:
raise AttributeError("User has no distinguishedName set.")
raise AttributeError(f"User has no {LDAP_DISTINGUISHED_NAME} set.")
self._source.connection.extend.microsoft.modify_password(user_dn, password)
def _ad_check_password_existing(self, password: str, user_dn: str) -> bool:
@ -117,9 +118,9 @@ class LDAPPasswordChanger:
"""
if user:
# Check if password contains sAMAccountName or displayNames
if "distinguishedName" in user.attributes:
if LDAP_DISTINGUISHED_NAME in user.attributes:
existing_user_check = self._ad_check_password_existing(
password, user.attributes.get("distinguishedName")
password, user.attributes.get(LDAP_DISTINGUISHED_NAME)
)
if not existing_user_check:
LOGGER.debug("Password failed name check", user=user)

View File

@ -1,191 +0,0 @@
"""Sync LDAP Users and groups into authentik"""
from typing import Any, Dict
import ldap3
import ldap3.core.exceptions
from django.db.utils import IntegrityError
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.models import Group, User
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
LOGGER = get_logger()
class LDAPSynchronizer:
"""Sync LDAP Users and groups into authentik"""
_source: LDAPSource
def __init__(self, source: LDAPSource):
self._source = source
@property
def base_dn_users(self) -> str:
"""Shortcut to get full base_dn for user lookups"""
if self._source.additional_user_dn:
return f"{self._source.additional_user_dn},{self._source.base_dn}"
return self._source.base_dn
@property
def base_dn_groups(self) -> str:
"""Shortcut to get full base_dn for group lookups"""
if self._source.additional_group_dn:
return f"{self._source.additional_group_dn},{self._source.base_dn}"
return self._source.base_dn
def sync_groups(self) -> int:
"""Iterate over all LDAP Groups and create authentik_core.Group instances"""
if not self._source.sync_groups:
LOGGER.warning("Group syncing is disabled for this Source")
return -1
groups = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_groups,
search_filter=self._source.group_object_filter,
search_scope=ldap3.SUBTREE,
attributes=ldap3.ALL_ATTRIBUTES,
)
group_count = 0
for group in groups:
attributes = group.get("attributes", {})
if self._source.object_uniqueness_field not in attributes:
LOGGER.warning(
"Cannot find uniqueness Field in attributes", user=attributes.keys()
)
continue
uniq = attributes[self._source.object_uniqueness_field]
_, created = Group.objects.update_or_create(
attributes__ldap_uniq=uniq,
parent=self._source.sync_parent_group,
defaults={
"name": attributes.get("name", ""),
"attributes": {
"ldap_uniq": uniq,
"distinguishedName": attributes.get("distinguishedName"),
},
},
)
LOGGER.debug(
"Synced group", group=attributes.get("name", ""), created=created
)
group_count += 1
return group_count
def sync_users(self) -> int:
"""Iterate over all LDAP Users and create authentik_core.User instances"""
if not self._source.sync_users:
LOGGER.warning("User syncing is disabled for this Source")
return -1
users = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_users,
search_filter=self._source.user_object_filter,
search_scope=ldap3.SUBTREE,
attributes=ldap3.ALL_ATTRIBUTES,
)
user_count = 0
for user in users:
attributes = user.get("attributes", {})
if self._source.object_uniqueness_field not in attributes:
LOGGER.warning(
"Cannot find uniqueness Field in attributes", user=user.keys()
)
continue
uniq = attributes[self._source.object_uniqueness_field]
try:
defaults = self._build_object_properties(attributes)
user, created = User.objects.update_or_create(
attributes__ldap_uniq=uniq,
defaults=defaults,
)
except IntegrityError as exc:
LOGGER.warning("Failed to create user", exc=exc)
LOGGER.warning(
(
"To merge new User with existing user, set the User's "
f"Attribute 'ldap_uniq' to '{uniq}'"
)
)
else:
if created:
user.set_unusable_password()
user.save()
LOGGER.debug(
"Synced User", user=attributes.get("name", ""), created=created
)
user_count += 1
return user_count
def sync_membership(self):
"""Iterate over all Users and assign Groups using memberOf Field"""
users = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_users,
search_filter=self._source.user_object_filter,
search_scope=ldap3.SUBTREE,
attributes=[
self._source.user_group_membership_field,
self._source.object_uniqueness_field,
],
)
group_cache: Dict[str, Group] = {}
for user in users:
member_of = user.get("attributes", {}).get(
self._source.user_group_membership_field, []
)
uniq = user.get("attributes", {}).get(
self._source.object_uniqueness_field, []
)
for group_dn in member_of:
# Check if group_dn is within our base_dn_groups, and skip if not
if not group_dn.endswith(self.base_dn_groups):
continue
# Check if we fetched the group already, and if not cache it for later
if group_dn not in group_cache:
groups = Group.objects.filter(
attributes__distinguishedName=group_dn
)
if not groups.exists():
LOGGER.warning(
"Group does not exist in our DB yet, run sync_groups first.",
group=group_dn,
)
return
group_cache[group_dn] = groups.first()
group = group_cache[group_dn]
users = User.objects.filter(attributes__ldap_uniq=uniq)
group.users.add(*list(users))
# Now that all users are added, lets write everything
for _, group in group_cache.items():
group.save()
LOGGER.debug("Successfully updated group membership")
def _build_object_properties(
self, attributes: Dict[str, Any]
) -> Dict[str, Dict[Any, Any]]:
properties = {"attributes": {}}
for mapping in self._source.property_mappings.all().select_subclasses():
if not isinstance(mapping, LDAPPropertyMapping):
continue
mapping: LDAPPropertyMapping
try:
value = mapping.evaluate(user=None, request=None, ldap=attributes)
if value is None:
continue
object_field = mapping.object_field
if object_field.startswith("attributes."):
properties["attributes"][
object_field.replace("attributes.", "")
] = value
else:
properties[object_field] = value
except PropertyMappingExpressionException as exc:
LOGGER.warning("Mapping failed to evaluate", exc=exc, mapping=mapping)
continue
if self._source.object_uniqueness_field in attributes:
properties["attributes"]["ldap_uniq"] = attributes.get(
self._source.object_uniqueness_field
)
properties["attributes"]["distinguishedName"] = attributes.get(
"distinguishedName"
)
return properties

View File

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