Compare commits

..

157 Commits

Author SHA1 Message Date
1a6ea72c09 release: 2021.9.4 2021-10-01 09:51:51 +02:00
c251b87f8c sources/ldap: add support for Active Directory userAccountControl attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:34:43 +02:00
21a9aa229a sources/ldap: don't sync ldap source when no property mappings are set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:34:43 +02:00
5f6565ee27 web/admin: fix LDAP Source form not exposing syncParentGroup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:34:43 +02:00
afad55a357 build(deps): bump sentry-sdk from 1.4.2 to 1.4.3 (#1502) 2021-09-30 19:34:43 +02:00
f25d76fa43 build(deps): bump sentry-sdk from 1.4.1 to 1.4.2 (#1488) 2021-09-30 19:34:42 +02:00
10b45d954e outposts: allow disabling of docker controller port mapping
closes #1474

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 00:11:50 +02:00
339eaf37f2 web/elements: use dedicated button for search clear instead of webkit exclusive one
closes #1499

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 00:11:50 +02:00
f723fdd551 web/elements: fix initialLoad not being done when viewportCheck was disabled
closes #1497

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 00:11:50 +02:00
8359f0bfb3 web: fix linting again
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:20:40 +02:00
ee610a906a web: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:07:04 +02:00
828eeb5ebb root: Use fully qualified names for docker bases base images. (#1490)
Signed-off-by: Steven Armstrong <steven@armstrong.cc>
2021-09-29 10:04:27 +02:00
c9c177d8f9 web/admin: don't require username nor name for activate/deactivate toggles
closes #1491

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:02:06 +02:00
c19afa4f16 outposts/proxy: fix duplicate protocol in domain auth mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:02:01 +02:00
941bc61b31 release: 2021.9.3 2021-09-27 17:31:50 +02:00
282b364606 stages/prompt: fix inconsistent policy context for validation policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 17:05:26 +02:00
ad4bc4083d website/docs: update dev docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 16:04:41 +02:00
ebe282eb1a web/admin: fix user_write form not writing group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 10:12:45 +02:00
830c26ca25 tests/e2e: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 09:52:47 +02:00
ed3b4a3d4a build(deps): bump rapidoc from 9.1.2 to 9.1.3 in /website (#1478)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.2 to 9.1.3.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.2...v9.1.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:24:43 +02:00
975c4ddc04 build(deps): bump postcss from 8.3.7 to 8.3.8 in /website (#1479)
Bumps [postcss](https://github.com/postcss/postcss) from 8.3.7 to 8.3.8.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.3.7...8.3.8)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:49 +02:00
7e2896298a build(deps): bump rapidoc from 9.1.2 to 9.1.3 in /web (#1480)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.2 to 9.1.3.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.2...v9.1.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:31 +02:00
cba9cf8361 build(deps): bump actions/github-script from 4.1 to 5 (#1481)
Bumps [actions/github-script](https://github.com/actions/github-script) from 4.1 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4.1...v5)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:21 +02:00
bf12580f64 build(deps): bump pycryptodome from 3.10.3 to 3.10.4 (#1482)
Bumps [pycryptodome](https://github.com/Legrandin/pycryptodome) from 3.10.3 to 3.10.4.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/commits/v3.10.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:03 +02:00
75ef4ce596 tests/e2e: add new ldap object classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:57:42 +02:00
c2f3ce11b0 outposts/ldap: fix potential panic when converting attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:52:25 +02:00
3c256fecc6 outposts/ldap: add groupofuniquenames
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:49:11 +02:00
0285b84133 outposts/ldap: add query support for all supported object classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:42:26 +02:00
99a371a02c web/elements: fix token copy button not working on chrome...
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:34:28 +02:00
c7e6eb8896 outposts/ldap: add support for base scope and domain info
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:01:22 +02:00
674bd9e05c web/admin: Fix typo 'username address' -> 'username' (#1473) 2021-09-26 12:53:37 +02:00
b79901df87 website/docs: prepare 2021.9.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 12:03:10 +02:00
b248f450dd outposts: make AUTHENTIK_HOST_BROWSER configurable from central config
closes #1471

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 12:00:51 +02:00
05db9e5c40 web/admin: handle error correctly when creating user recovery link
closes #1472

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 11:49:40 +02:00
234a5e2b66 outposts: fix outposts not correctly updating central state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 11:40:21 +02:00
aea1736f70 outposts/proxy: Fix failing traefik healtcheck (#1470) 2021-09-26 11:33:18 +02:00
9f4a4449f5 outposts/proxy: ensure cookies only last as long as tokens
closes #1462

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 16:12:59 +02:00
b6b55e2336 build(deps): bump goauthentik.io/api from 0.202192.3 to 0.202192.5 (#1468)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202192.3 to 0.202192.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202192.3...v0.202192.5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-25 16:06:10 +02:00
8f2805e05b web: Update Web API Client version (#1467)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 16:04:07 +02:00
4f3583cd7e providers/proxy: make token_validity float and optional for backwards compat
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 15:54:32 +02:00
617e90dca3 web: Update Web API Client version (#1465)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 15:48:05 +02:00
f7408626a8 providers/proxy: return token_validity as total seconds instead of expression
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 15:44:16 +02:00
4dcb15af46 build(deps): bump goauthentik.io/api from 0.202192.1 to 0.202192.3 (#1464)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202192.1 to 0.202192.3.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202192.1...v0.202192.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-25 15:11:08 +02:00
89beb7a9f7 web: Update Web API Client version (#1463)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 15:02:33 +02:00
28eeb4798e providers/proxy: add token_validity field for outpost configuration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1462
2021-09-25 15:00:06 +02:00
79b92e764e *: fix typos in code
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 00:01:11 +02:00
919336a519 outposts: ensure service is always re-created with mismatching ports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 23:45:15 +02:00
27e04589c1 outposts/proxyv2: fix routing not working correctly for domain auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 23:32:16 +02:00
ba44fbdac8 website/docs: fix typos and grammar (#1459) 2021-09-24 15:37:54 +02:00
0e093a8917 web: Update Web API Client version (#1458) 2021-09-24 12:23:14 +02:00
d0bfb99859 web/elements: improve error handling on forms
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 12:19:56 +02:00
93bdea3769 core: fix api return code for user self-update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:51:03 +02:00
e681654af7 web/admin: add notice for recovery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:50:52 +02:00
cab7593dca web/user: fix brand not being shown in safari
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:50:46 +02:00
cf92f9aefc web/elements: fix token copy error in safari
closes #1219

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 10:44:28 +02:00
8d72b3498d internal: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 10:44:28 +02:00
42ab858c50 build(deps): bump goauthentik.io/api from 0.202191.4 to 0.202192.1 (#1455)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202191.4 to 0.202192.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202191.4...v0.202192.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-24 09:34:10 +02:00
a1abae9ab1 build(deps): bump boto3 from 1.18.46 to 1.18.47 (#1456)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.46 to 1.18.47.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.46...1.18.47)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-24 09:34:01 +02:00
8f36b49061 web/user: search apps when user typed before apps have loaded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:34:11 +02:00
64b4e851ce events: add additional validation for event transport
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:29:58 +02:00
40a62ac1e5 web/admin: fix Transport Form not loading mode correctly on edit
closes #1453

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:16:38 +02:00
5df60e4d87 web/admin: fix NotificationWebhookMapping not loading correctly
closes #1452

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:13:58 +02:00
50ebc8522d web: Update Web API Client version (#1454)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-23 14:21:49 +02:00
eddca478dc release: 2021.9.2 2021-09-23 12:34:02 +02:00
99a7fca08e website/docs: prepare 2021.9.2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 12:33:42 +02:00
a7e3602908 web: fix import order of polyfills causing shadydom to not work on firefox and safari
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 10:16:49 +02:00
74169860cf api: add logging to sentry proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 09:57:42 +02:00
52bb774f73 internal: add asset paths for user interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 09:57:24 +02:00
f26fcaf825 website/docs: add warning for example flows (#1444) 2021-09-23 08:34:40 +02:00
b8e92e2f11 build(deps): bump postcss from 8.3.6 to 8.3.7 in /website (#1445) 2021-09-23 08:33:27 +02:00
08adfc94d6 build(deps): bump rollup from 2.56.3 to 2.57.0 in /web (#1446) 2021-09-23 08:33:18 +02:00
236fafb735 build(deps): bump boto3 from 1.18.45 to 1.18.46 (#1447) 2021-09-23 08:33:10 +02:00
5ad9ddee3c build(deps): bump goauthentik.io/api from 0.202191.3 to 0.202191.4 (#1449) 2021-09-23 08:33:01 +02:00
24d220ff49 build(deps): bump urllib3 from 1.26.6 to 1.26.7 (#1448) 2021-09-23 08:32:53 +02:00
3364c195b7 build(deps): bump sentry-sdk from 1.4.0 to 1.4.1 (#1450) 2021-09-23 08:32:43 +02:00
50aa87d141 web/user: enable sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 22:35:52 +02:00
72b375023d web: Update Web API Client version (#1443)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-22 20:53:25 +02:00
77ba186818 website/docs: add notice for guacamole token length
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 20:02:30 +02:00
2fe6de0505 release: 2021.9.1 2021-09-22 19:11:20 +02:00
bf9e969b53 website/docs: prepare 2021.9.1
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 18:58:52 +02:00
184f119b16 website: set use_global_settings to true for example flows with email stages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 18:52:55 +02:00
ebc06f1abe outposts/ldap: fix logic error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 13:19:50 +02:00
0f8880ab0a outposts: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 13:14:28 +02:00
ee56da5092 build(deps): bump @sentry/tracing from 6.13.1 to 6.13.2 in /web (#1438) 2021-09-22 07:32:40 +02:00
2152004502 build(deps): bump @types/codemirror from 5.60.2 to 5.60.3 in /web (#1437) 2021-09-22 07:30:35 +02:00
45d0b80d02 build(deps): bump @sentry/browser from 6.13.1 to 6.13.2 in /web (#1439) 2021-09-22 07:30:27 +02:00
96065eb942 build(deps): bump boto3 from 1.18.44 to 1.18.45 (#1441) 2021-09-22 07:30:01 +02:00
ac944fee8b build(deps): bump drf-spectacular from 0.18.2 to 0.19.0 (#1442) 2021-09-22 07:29:52 +02:00
1d0e5fc353 build(deps): bump sentry-sdk from 1.3.1 to 1.4.0 (#1440) 2021-09-22 07:28:48 +02:00
1f97420207 outposts/ldap: allow custom attributes to shadow built-in attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 21:59:39 +02:00
ae07f13a87 outposts: don't map port 9300 on docker, only expose port
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 21:40:08 +02:00
0aec504170 website/docs: add ssl port for ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 15:44:05 +02:00
3b4c9bcc57 root: use tagged go client version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 15:42:07 +02:00
5182a6741e root: format pyproject
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 13:32:28 +02:00
da7635ae5c web: sort imports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 11:33:51 +02:00
a92a0fb60a web: migrate to lit 2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 11:19:26 +02:00
cb10c1753b build(deps): bump lit-html from 1.4.1 to 2.0.0 in /web (#1427) 2021-09-21 08:35:36 +02:00
ae654bd4c8 build(deps): bump lit-element from 2.5.1 to 3.0.0 in /web (#1433) 2021-09-21 08:32:15 +02:00
28192655ec build(deps): bump @typescript-eslint/eslint-plugin in /web (#1426) 2021-09-21 08:32:00 +02:00
9582294eb8 build(deps): bump @sentry/tracing from 6.12.0 to 6.13.1 in /web (#1428) 2021-09-21 08:31:48 +02:00
0172430d7d build(deps): bump @patternfly/patternfly from 4.132.2 to 4.135.2 in /web (#1429) 2021-09-21 08:30:44 +02:00
1454b65933 build(deps): bump @typescript-eslint/parser in /web (#1430) 2021-09-21 08:30:36 +02:00
432a7792e2 build(deps): bump @sentry/browser from 6.12.0 to 6.13.1 in /web (#1431) 2021-09-21 08:30:28 +02:00
54069618b4 build(deps): bump codemirror from 5.62.3 to 5.63.0 in /web (#1432) 2021-09-21 08:30:20 +02:00
81feb313df build(deps): bump geoip2 from 4.2.0 to 4.3.0 (#1434) 2021-09-21 08:29:33 +02:00
e6b275add3 stages/invitation: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 20:41:05 +02:00
27016a5527 stages/invitation: fix tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 20:30:51 +02:00
4c29d517f0 stages/email: use different query arguments for email and invitation tokens
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:55:53 +02:00
180d27cc37 outposts: don't restart container when health checks are starting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:46:05 +02:00
5a8b356dc7 web: fix css for dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:38:25 +02:00
3195640776 stages/email: slugify token identifier
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:26:25 +02:00
f463296d47 web: Update Web API Client version (#1421)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-20 19:22:46 +02:00
adf4b23c01 website/docs: add /akprox for nginx auth_request (#1420) 2021-09-20 19:21:30 +02:00
d900a2b6a9 *: fix lookup_fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:19:36 +02:00
95a2fddfa8 policies/expression: add ak_user_has_authenticator
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:13:41 +02:00
8f7d21b692 stages/email: don't throw 404 when token can't be found
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:01:25 +02:00
3f84abec2f core: fix token identifier not being slugified when created with user-controller input
closes #1390

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 13:43:25 +02:00
b5c857aff4 api: add explicit lookup_value_regex, disable include_format_suffixes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 13:42:56 +02:00
f8dee09107 web/user: allow customisable background colour
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 12:49:17 +02:00
84a800583c web/user: make search configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 12:15:53 +02:00
88de94f014 build(deps): bump rapidoc from 9.1.0 to 9.1.2 in /website (#1418)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.0 to 9.1.2.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.0...v9.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 11:50:38 +02:00
25549ec339 build(deps): bump rapidoc from 9.1.0 to 9.1.2 in /web (#1419)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.0 to 9.1.2.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.0...v9.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 11:50:27 +02:00
fe4923bff6 build(deps): bump boto3 from 1.18.43 to 1.18.44 (#1417)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.43 to 1.18.44.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.43...1.18.44)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 09:09:22 +02:00
bb1a0b6bd2 web: Update Web API Client version (#1416)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-19 22:32:38 +02:00
879b5ead71 web: fix notification badge not refreshing after clearing notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:58:59 +02:00
1670ec9167 website/docs: update 2021.9.1-rc3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:55:21 +02:00
ac52667327 release: 2021.9.1-rc3 2021-09-19 21:52:49 +02:00
0d7c5c2108 web/user: fix edit button for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:10:57 +02:00
73e3d19384 web/user: add auto-focus search for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:07:18 +02:00
f6e0f0282d core: fix tokens not being viewable but superusers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 16:11:20 +02:00
3f42067a8f web: improve display of action buttons with non-primary classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:57:12 +02:00
ed6f5b98df sources/ldap: improve messages of sync tasks in UI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:54:22 +02:00
dd290e264c web/admin: trigger refresh after syncing ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:34:44 +02:00
c85484fc00 core: allow admins to create tokens with all parameters, re-add user to token form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:25:48 +02:00
663dffd8be web/admin: fix error in firefox when creating token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:28:40 +02:00
c15d0c3d17 web: fix text colour in delete form in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:14:50 +02:00
bf09a54f35 web/user: optimise load, fix unread status for notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 18:16:31 +02:00
930dd51663 web: fix datetime-local fields throwing errors on firefox
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 15:31:48 +02:00
12a523c7aa website: remove sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 14:33:53 +02:00
ea9a6d57dd build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /website (#1414)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:54 +02:00
91958e1232 build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /web (#1415)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:44 +02:00
8925afb089 web/admin: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:54:48 +02:00
ccafe7be4f website/docs: update 2021.9 changelog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:36 +02:00
8279690a8f sources/ldap: prevent error when retrying old system task with no arguments
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:26 +02:00
763d3ae76a web/admin: fix ldap sync status for new API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:46:18 +02:00
b775e7f4d3 web: Update Web API Client version (#1412)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-17 12:45:16 +02:00
3d8d93ece5 root: log failed celery tasks to event log
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:42 +02:00
06af306e8a sources/ldap: bump timeout, run each sync component in its own task
closes #1411

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:20 +02:00
9257f3c919 web/user: add missing stop impersonation button
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 10:04:47 +02:00
2fe7f4cf04 web/user: fix final redirect after stage setup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:14 +02:00
04399bc8bb web/admin: fix settings link on user avatar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:04 +02:00
fcbcfbc3c0 website/docs: Minor LDAP and NGINX Documentation Updates (#1406)
* update LDAP documentation

* include domain level nginx forward auth example

* wrap in banner

* update placeholder
2021-09-17 09:47:27 +02:00
3e4ce62dfe build(deps-dev): bump pylint from 2.10.2 to 2.11.1 (#1409)
* build(deps-dev): bump pylint from 2.10.2 to 2.11.1

Bumps [pylint](https://github.com/PyCQA/pylint) from 2.10.2 to 2.11.1.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/v2.10.2...v2.11.1)

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

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

* root: update pylint config

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:46:39 +02:00
d8292151e6 build(deps-dev): bump prettier from 2.4.0 to 2.4.1 in /website (#1407) 2021-09-17 09:17:20 +02:00
3d01a59b34 build(deps): bump prettier from 2.4.0 to 2.4.1 in /web (#1408) 2021-09-17 09:17:12 +02:00
5df15c4105 build(deps): bump boto3 from 1.18.42 to 1.18.43 (#1410) 2021-09-17 09:16:43 +02:00
75d695105d web: Update Web API Client version (#1405)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-16 23:44:53 +02:00
365 changed files with 4984 additions and 3508 deletions

View File

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

1
.github/codespell-words.txt vendored Normal file
View File

@ -0,0 +1 @@
keypair

View File

@ -61,7 +61,7 @@ jobs:
npm install
- name: Generate API
run: make gen-web
- name: prettier
- name: lit-analyse
run: |
cd web
npm run lit-analyse

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.9.1-rc2,
beryju/authentik:2021.9.4,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.9.1-rc2,
ghcr.io/goauthentik/server:2021.9.4,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.9.4', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.9.1-rc2,
beryju/authentik-proxy:2021.9.4,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.9.1-rc2,
ghcr.io/goauthentik/proxy:2021.9.4,
ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.9.4', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.9.1-rc2,
beryju/authentik-ldap:2021.9.4,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.9.1-rc2,
ghcr.io/goauthentik/ldap:2021.9.4,
ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.1-rc2', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.9.4', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -175,7 +175,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.9.1-rc2
version: authentik@2021.9.4
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -27,7 +27,7 @@ jobs:
docker-compose run -u root server test
- name: Extract version number
id: get_version
uses: actions/github-script@v4.1
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@ -1,5 +1,5 @@
# Stage 1: Lock python dependencies
FROM python:3.9-slim-buster as locker
FROM docker.io/python:3.9-slim-buster as locker
COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/
@ -11,7 +11,7 @@ RUN pip install pipenv && \
pipenv lock -r --dev-only > requirements-dev.txt
# Stage 2: Build website
FROM node as website-builder
FROM docker.io/node as website-builder
COPY ./website /static/
@ -19,7 +19,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-docs-only
# Stage 3: Build webui
FROM node as web-builder
FROM docker.io/node as web-builder
COPY ./web /static/
@ -27,7 +27,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
# Stage 4: Build go proxy
FROM golang:1.17.1 AS builder
FROM docker.io/golang:1.17.1 AS builder
WORKDIR /work
@ -47,7 +47,7 @@ COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
FROM python:3.9-slim-buster
FROM docker.io/python:3.9-slim-buster
WORKDIR /
COPY --from=locker /app/requirements.txt /

View File

@ -20,6 +20,11 @@ test:
lint-fix:
isort authentik tests lifecycle
black authentik tests lifecycle
codespell -I .github/codespell-words.txt -w authentik
codespell -I .github/codespell-words.txt -w internal
codespell -I .github/codespell-words.txt -w cmd
codespell -I .github/codespell-words.txt -w web/src
codespell -I .github/codespell-words.txt -w website/src
lint:
pyright authentik tests lifecycle

View File

@ -48,6 +48,7 @@ duo-client = "*"
ua-parser = "*"
deepmerge = "*"
colorama = "*"
codespell = "*"
[dev-packages]
bandit = "*"

323
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "19d5324fd1a4af125ed57a683030ca14ee2d3648117748e4b32656875484728e"
"sha256": "babb6061c555f8f239f00210b2a0356763bdaaca2f3d704cf3444891b84db84d"
},
"pipfile-spec": 6,
"requires": {},
@ -120,27 +120,27 @@
},
"boto3": {
"hashes": [
"sha256:63b9846c26e0905f4e9e39d6b59f152330c53a926d693439161c43dcf9779365",
"sha256:a9232185d8e7e2fd2b166c0ebee5d7b1f787fdb3093f33bbf5aa932c08f0ccac"
"sha256:7b45b224442c479de4bc6e6e9cb0557b642fc7a77edc8702e393ccaa2e0aa128",
"sha256:c388da7dc1a596755f39de990a72e05cee558d098e81de63de55bd9598cc5134"
],
"index": "pypi",
"version": "==1.18.42"
"version": "==1.18.48"
},
"botocore": {
"hashes": [
"sha256:0952d1200968365b440045efe8e45bbae38cf603fee12bcfc3d7b5f963cbfa18",
"sha256:6de4fec4ee10987e4dea96f289553c2f45109fcaafcb74a5baee1221926e1306"
"sha256:17a10dd33334e7e3aaa4e12f66317284f96bb53267e20bc877a187c442681772",
"sha256:2089f9fa36a59d8c02435c49d58ccc7b3ceb9c0c054ea4f71631c3c3a1c5245e"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.42"
"version": "==1.21.51"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
"sha256:0a3d3556c2c3befdbba2f93b78792c199c66201c999e97947ea0b7437758246b",
"sha256:6a6fa6802188ab7e77bab2db001d676e854499552b0037d999d5b9f211db5250"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
"version": "==4.2.3"
},
"cbor2": {
"hashes": [
@ -252,11 +252,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd"
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
],
"markers": "python_version >= '3'",
"version": "==2.0.5"
"version": "==2.0.6"
},
"click": {
"hashes": [
@ -268,9 +268,11 @@
},
"click-didyoumean": {
"hashes": [
"sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
],
"version": "==0.0.3"
"markers": "python_version < '4' and python_full_version >= '3.6.2'",
"version": "==0.3.0"
},
"click-plugins": {
"hashes": [
@ -286,6 +288,14 @@
],
"version": "==0.2.0"
},
"codespell": {
"hashes": [
"sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5",
"sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"
],
"index": "pypi",
"version": "==2.1.0"
},
"colorama": {
"hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
@ -303,25 +313,28 @@
},
"cryptography": {
"hashes": [
"sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e",
"sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b",
"sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
"sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
"sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
"sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
"sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
"sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
"sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
"sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
"sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb",
"sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14",
"sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af",
"sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e",
"sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5",
"sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06",
"sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"
"sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6",
"sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6",
"sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c",
"sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999",
"sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e",
"sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992",
"sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d",
"sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588",
"sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa",
"sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d",
"sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd",
"sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d",
"sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953",
"sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2",
"sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8",
"sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6",
"sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9",
"sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6",
"sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad",
"sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"
],
"version": "==3.4.8"
"version": "==35.0.0"
},
"dacite": {
"hashes": [
@ -369,11 +382,11 @@
},
"django-filter": {
"hashes": [
"sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06",
"sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"
"sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e",
"sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"
],
"index": "pypi",
"version": "==2.4.0"
"version": "==21.1"
},
"django-guardian": {
"hashes": [
@ -449,11 +462,11 @@
},
"drf-spectacular": {
"hashes": [
"sha256:47ef6ec8ff48ac8aede6ec12450a55fee381cf84de969ef1724dcde5a93de6b8",
"sha256:d746b936cb4cddec380ea95bf91de6a6721777dfc42e0eea53b83c61a625e94e"
"sha256:65df818226477cdfa629947ea52bc0cc13eb40550b192eeccec64a6b782651fd",
"sha256:f71205da3645d770545abeaf48e8a15afd6ee9a76e57c03df4592e51be1059bf"
],
"index": "pypi",
"version": "==0.18.2"
"version": "==0.19.0"
},
"duo-client": {
"hashes": [
@ -480,19 +493,19 @@
},
"geoip2": {
"hashes": [
"sha256:906a1dbf15a179a1af3522970e8420ab15bb3e0afc526942cc179e12146d9c1d",
"sha256:b97b44031fdc463e84eb1316b4f19edd978cb1d78703465fcb1e36dc5a822ba6"
"sha256:f150bed3190d543712a17467208388d31bd8ddb49b2226fba53db8aaedb8ba89",
"sha256:f9172cdfb2a5f9225ace5e30dd7426413ad28798a5f474cd1538780686bd6a87"
],
"index": "pypi",
"version": "==4.2.0"
"version": "==4.4.0"
},
"google-auth": {
"hashes": [
"sha256:7ae5eda089d393ca01658b550df24913cbbbdd34e9e6dedc1cea747485ae0c04",
"sha256:bde03220ed56e4e147dec92339c90ce95159dce657e2cccd0ac1fe82f6a96284"
"sha256:2a92b485afed5292946b324e91fcbe03db277ee4cb64c998c6cfa66d4af01dee",
"sha256:6dc8173abd50f25b6e62fc5b42802c96fc7cd9deb9bfeeb10a79f5606225cdf4"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.0"
"version": "==2.2.1"
},
"gunicorn": {
"hashes": [
@ -616,10 +629,10 @@
},
"jsonschema": {
"hashes": [
"sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
"sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
"sha256:bc51325b929171791c42ebc1c70b9713eb134d3bb8ebd5474c8b659b15be6d86",
"sha256:c773028c649441ab980015b5b622f4cd5134cf563daaf0235ca4b73cc3734f20"
],
"version": "==3.2.0"
"version": "==4.0.0"
},
"kombu": {
"hashes": [
@ -704,10 +717,10 @@
},
"maxminddb": {
"hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
"sha256:e37707ec4fab115804670e0fb7aedb4b57075a8b6f80052bdc648d3c005184e5"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.3"
"version": "==2.2.0"
},
"msgpack": {
"hashes": [
@ -898,39 +911,39 @@
},
"pycryptodome": {
"hashes": [
"sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0",
"sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d",
"sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce",
"sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06",
"sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35",
"sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27",
"sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129",
"sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9",
"sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673",
"sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1",
"sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6",
"sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8",
"sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c",
"sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713",
"sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6",
"sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438",
"sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e",
"sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07",
"sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6",
"sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd",
"sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6",
"sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8",
"sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427",
"sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067",
"sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8",
"sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b",
"sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa",
"sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf",
"sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da",
"sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"
"sha256:04e14c732c3693d2830839feed5129286ce47ffa8bfe90e4ae042c773e51c677",
"sha256:11d3164fb49fdee000fde05baecce103c0c698168ef1a18d9c7429dd66f0f5bb",
"sha256:217dcc0c92503f7dd4b3d3b7d974331a4419f97f555c99a845c3b366fed7056b",
"sha256:24c1b7705d19d8ae3e7255431efd2e526006855df62620118dd7b5374c6372f6",
"sha256:309529d2526f3fb47102aeef376b3459110a6af7efb162e860b32e3a17a46f06",
"sha256:3a153658d97258ca20bf18f7fe31c09cc7c558b6f8974a6ec74e19f6c634bd64",
"sha256:3f9fb499e267039262569d08658132c9cd8b136bf1d8c56b72f70ed05551e526",
"sha256:3faa6ebd35c61718f3f8862569c1f38450c24f3ededb213e1a64806f02f584bc",
"sha256:40083b0d7f277452c7f2dd4841801f058cc12a74c219ee4110d65774c6a58bef",
"sha256:49e54f2245befb0193848c8c8031d8d1358ed4af5a1ae8d0a3ba669a5cdd3a72",
"sha256:4e8fc4c48365ce8a542fe48bf1360da05bb2851df12f64fc94d751705e7cdbe7",
"sha256:54d4e4d45f349d8c4e2f31c2734637ff62a844af391b833f789da88e43a8f338",
"sha256:66301e4c42dee43ee2da256625d3fe81ef98cc9924c2bd535008cc3ad8ded77b",
"sha256:6b45fcace5a5d9c57ba87cf804b161adc62aa826295ce7f7acbcbdc0df74ed37",
"sha256:7efec2418e9746ec48e264eea431f8e422d931f71c57b1c96ee202b117f58fa9",
"sha256:851e6d4930b160417235955322db44adbdb19589918670d63f4acd5d92959ac0",
"sha256:8e82524e7c354033508891405574d12e612cc4fdd3b55d2c238fc1a3e300b606",
"sha256:8ec154ec445412df31acf0096e7f715e30e167c8f2318b8f5b1ab7c28f4c82f7",
"sha256:91ba4215a1f37d0f371fe43bc88c5ff49c274849f3868321c889313787de7672",
"sha256:97e7df67a4da2e3f60612bbfd6c3f243a63a15d8f4797dd275e1d7b44a65cb12",
"sha256:9a2312440057bf29b9582f72f14d79692044e63bfbc4b4bbea8559355f44f3dd",
"sha256:a7471646d8cd1a58bb696d667dcb3853e5c9b341b68dcf3c3cc0893d0f98ca5f",
"sha256:ac3012c36633564b2b5539bb7c6d9175f31d2ce74844e9abe654c428f02d0fd8",
"sha256:b1daf251395af7336ddde6a0015ba5e632c18fe646ba930ef87402537358e3b4",
"sha256:b217b4525e60e1af552d62bec01b4685095436d4de5ecde0f05d75b2f95ba6d4",
"sha256:c61ea053bd5d4c12a063d7e704fbe1c45abb5d2510dab55bd95d166ba661604f",
"sha256:c6469d1453f5864e3321a172b0aa671b938d753cbf2376b99fa2ab8841539bb8",
"sha256:cefe6b267b8e5c3c72e11adec35a9c7285b62e8ea141b63e87055e9a9e5f2f8c",
"sha256:d713dc0910e5ded07852a05e9b75f1dd9d3a31895eebee0668f612779b2a748c",
"sha256:db15fa07d2a4c00beeb5e9acdfdbc1c79f9ccfbdc1a8f36c82c4aa44951b33c9"
],
"index": "pypi",
"version": "==3.10.1"
"version": "==3.10.4"
},
"pyjwt": {
"hashes": [
@ -942,10 +955,10 @@
},
"pyopenssl": {
"hashes": [
"sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51",
"sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"
"sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3",
"sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"
],
"version": "==20.0.1"
"version": "==21.0.0"
},
"pyparsing": {
"hashes": [
@ -1082,11 +1095,11 @@
},
"sentry-sdk": {
"hashes": [
"sha256:ebe99144fa9618d4b0e7617e7929b75acd905d258c3c779edcd34c0adfffe26c",
"sha256:f33d34c886d0ba24c75ea8885a8b3a172358853c7cbde05979fc99c29ef7bc52"
"sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828",
"sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"
],
"index": "pypi",
"version": "==1.3.1"
"version": "==1.4.3"
},
"service-identity": {
"hashes": [
@ -1176,11 +1189,11 @@
"secure"
],
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"index": "pypi",
"version": "==1.26.6"
"version": "==1.26.7"
},
"uvicorn": {
"extras": [
@ -1410,11 +1423,11 @@
},
"astroid": {
"hashes": [
"sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c",
"sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"
"sha256:dcc06f6165f415220013801642bd6c9808a02967070919c4b746c6864c205471",
"sha256:fe81f80c0b35264acb5653302ffbd935d394f1775c5e4487df745bf9c2442708"
],
"markers": "python_version ~= '3.6'",
"version": "==2.7.3"
"version": "==2.8.0"
},
"attrs": {
"hashes": [
@ -1457,11 +1470,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd"
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
],
"markers": "python_version >= '3'",
"version": "==2.0.5"
"version": "==2.0.6"
},
"click": {
"hashes": [
@ -1547,11 +1560,11 @@
},
"gitpython": {
"hashes": [
"sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
"sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
"sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647",
"sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"
],
"markers": "python_version >= '3.6'",
"version": "==3.1.18"
"markers": "python_version >= '3.7'",
"version": "==3.1.24"
},
"idna": {
"hashes": [
@ -1642,11 +1655,11 @@
},
"platformdirs": {
"hashes": [
"sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f",
"sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.0"
"version": "==2.4.0"
},
"pluggy": {
"hashes": [
@ -1666,11 +1679,11 @@
},
"pylint": {
"hashes": [
"sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1",
"sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"
"sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
"sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
],
"index": "pypi",
"version": "==2.10.2"
"version": "==2.11.1"
},
"pylint-django": {
"hashes": [
@ -1748,49 +1761,49 @@
},
"regex": {
"hashes": [
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
"sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656",
"sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a",
"sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae",
"sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0",
"sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1",
"sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7",
"sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3",
"sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c",
"sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7",
"sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3",
"sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673",
"sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c",
"sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e",
"sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d",
"sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2",
"sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72",
"sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3",
"sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11",
"sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90",
"sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5",
"sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec",
"sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a",
"sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561",
"sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab",
"sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078",
"sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc",
"sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1",
"sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c",
"sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6",
"sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48",
"sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456",
"sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8",
"sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18",
"sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4",
"sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736",
"sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949",
"sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346",
"sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db",
"sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad",
"sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9",
"sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"
],
"version": "==2021.8.28"
"version": "==2021.9.24"
},
"requests": {
"hashes": [
@ -1848,16 +1861,24 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"typing-extensions": {
"hashes": [
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
],
"version": "==3.10.0.2"
},
"urllib3": {
"extras": [
"secure"
],
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"index": "pypi",
"version": "==1.26.6"
"version": "==1.26.7"
},
"wrapt": {
"hashes": [

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.9.1-rc2"
__version__ = "2021.9.4"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -84,7 +84,7 @@ class SystemSerializer(PassiveSerializer):
return now()
def get_embedded_outpost_host(self, request: Request) -> str:
"""Get the FQDN configured on the embeddded outpost"""
"""Get the FQDN configured on the embedded outpost"""
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts.exists():
return ""

View File

@ -40,7 +40,6 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
raise AuthenticationFailed("Malformed header")
tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
if not tokens.exists():
LOGGER.info("Authenticating via secret_key")
user = token_secret_key(password)
if not user:
raise AuthenticationFailed("Token invalid/expired")
@ -58,6 +57,7 @@ def token_secret_key(value: str) -> Optional[User]:
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts:
return None
LOGGER.info("Authenticating via secret_key")
outpost = outposts.first()
return outpost.user

View File

@ -33,3 +33,12 @@ class OwnerPermissions(BasePermission):
if owner != request.user:
return False
return True
class OwnerSuperuserPermissions(OwnerPermissions):
"""Similar to OwnerPermissions, except always allow access for superusers"""
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
if request.user.is_superuser:
return True
return super().has_object_permission(request, view, obj)

View File

@ -5,6 +5,9 @@ from typing import Callable, Optional
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
LOGGER = get_logger()
def permission_required(perm: Optional[str] = None, other_perms: Optional[list[str]] = None):
@ -18,10 +21,12 @@ def permission_required(perm: Optional[str] = None, other_perms: Optional[list[s
if perm:
obj = self.get_object()
if not request.user.has_perm(perm, obj):
LOGGER.debug("denying access for object", user=request.user, perm=perm, obj=obj)
return self.permission_denied(request)
if other_perms:
for other_perm in other_perms:
if not request.user.has_perm(other_perm):
LOGGER.debug("denying access for other", user=request.user, perm=perm)
return self.permission_denied(request)
return func(self, request, *args, **kwargs)

View File

@ -11,7 +11,7 @@ from drf_spectacular.types import OpenApiTypes
def build_standard_type(obj, **kwargs):
"""Build a basic type with optional add ons."""
"""Build a basic type with optional add owns."""
schema = build_basic_type(obj)
schema.update(kwargs)
return schema

View File

@ -63,7 +63,7 @@ class ConfigView(APIView):
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrive public configuration options"""
"""Retrieve public configuration options"""
config = ConfigSerializer(
{
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"),

View File

@ -10,10 +10,13 @@ from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from structlog.stdlib import get_logger
from authentik.api.tasks import sentry_proxy
from authentik.lib.config import CONFIG
LOGGER = get_logger()
class PlainTextParser(BaseParser):
"""Plain text parser."""
@ -45,6 +48,7 @@ class SentryTunnelView(APIView):
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled
if not CONFIG.y_bool("error_reporting.enabled", False):
LOGGER.debug("error reporting disabled")
return HttpResponse(status=400)
# Body is 2 json objects separated by \n
full_body = request.body
@ -55,6 +59,7 @@ class SentryTunnelView(APIView):
# Check that the DSN is what we expect
dsn = header.get("dsn", "")
if dsn != settings.SENTRY_DSN:
LOGGER.debug("Invalid dsn", have=dsn, expected=settings.SENTRY_DSN)
return HttpResponse(status=400)
sentry_proxy.delay(full_body.decode())
return HttpResponse(status=204)

View File

@ -99,6 +99,7 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
from authentik.tenants.api import TenantViewSet
router = routers.DefaultRouter()
router.include_format_suffixes = False
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
router.register("admin/apps", AppsViewSet, basename="apps")

View File

@ -11,7 +11,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from ua_parser import user_agent_parser
from authentik.api.authorization import OwnerPermissions
from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER, GeoIPDict
@ -103,7 +103,7 @@ class AuthenticatedSessionViewSet(
search_fields = ["user__username", "last_ip", "last_user_agent"]
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
ordering = ["user__username"]
permission_classes = [OwnerPermissions]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
def get_queryset(self):

View File

@ -2,15 +2,19 @@
from typing import Any
from django.http.response import Http404
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
@ -79,13 +83,23 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"expires",
"expiring",
]
ordering = ["expires"]
ordering = ["identifier", "expires"]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)
def perform_create(self, serializer: TokenSerializer):
serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
if not self.request.user.is_superuser:
return serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
return super().perform_create(serializer)
@permission_required("authentik_core.view_token_key")
@extend_schema(

View File

@ -8,6 +8,7 @@ from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_filters.filters import BooleanFilter, CharFilter, ModelMultipleChoiceFilter
@ -273,7 +274,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
group.users.add(user)
token = Token.objects.create(
identifier=f"service-account-{username}-password",
identifier=slugify(f"service-account-{username}-password"),
intent=TokenIntents.INTENT_APP_PASSWORD,
user=user,
expires=now() + timedelta(days=360),
@ -307,7 +308,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""Allow users to change information on their own profile"""
data = UserSelfSerializer(instance=User.objects.get(pk=request.user.pk), data=request.data)
if not data.is_valid():
return Response(data.errors)
return Response(data.errors, status=400)
new_user = data.save()
# If we're impersonating, we need to update that user object
# since it caches the full object

View File

@ -26,7 +26,7 @@ class Migration(migrations.Migration):
),
(
"username_link",
"Link to a user with identical username address. Can have security implications when a username is used with another source.",
"Link to a user with identical username. Can have security implications when a username is used with another source.",
),
(
"username_deny",

View File

@ -283,7 +283,7 @@ class SourceUserMatchingModes(models.TextChoices):
)
USERNAME_LINK = "username_link", _(
(
"Link to a user with identical username address. Can have security implications "
"Link to a user with identical username. Can have security implications "
"when a username is used with another source."
)
)

View File

@ -1,4 +1,6 @@
"""Test token API"""
from json import loads
from django.urls.base import reverse
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
@ -13,7 +15,8 @@ class TestTokenAPI(APITestCase):
def setUp(self) -> None:
super().setUp()
self.user = User.objects.get(username="akadmin")
self.user = User.objects.create(username="testuser")
self.admin = User.objects.get(username="akadmin")
self.client.force_login(self.user)
def test_token_create(self):
@ -55,3 +58,29 @@ class TestTokenAPI(APITestCase):
clean_expired_models.delay().get()
token.refresh_from_db()
self.assertNotEqual(key, token.key)
def test_list(self):
"""Test Token List (Test normal authentication)"""
token_should: Token = Token.objects.create(
identifier="test", expiring=False, user=self.user
)
Token.objects.create(identifier="test-2", expiring=False, user=get_anonymous_user())
response = self.client.get(reverse(("authentik_api:token-list")))
body = loads(response.content)
self.assertEqual(len(body["results"]), 1)
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
def test_list_admin(self):
"""Test Token List (Test with admin auth)"""
self.client.force_login(self.admin)
token_should: Token = Token.objects.create(
identifier="test", expiring=False, user=self.user
)
token_should_not: Token = Token.objects.create(
identifier="test-2", expiring=False, user=get_anonymous_user()
)
response = self.client.get(reverse(("authentik_api:token-list")))
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
self.assertEqual(body["results"][1]["identifier"], token_should_not.identifier)

View File

@ -1,7 +1,10 @@
"""NotificationTransport API Views"""
from typing import Any
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
@ -29,6 +32,14 @@ class NotificationTransportSerializer(ModelSerializer):
"""Return selected mode with a UI Label"""
return TransportMode(instance.mode).label
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure the required fields are set."""
mode = attrs.get("mode")
if mode in [TransportMode.WEBHOOK, TransportMode.WEBHOOK_SLACK]:
if "webhook_url" not in attrs or attrs.get("webhook_url", "") == "":
raise ValidationError("Webhook URL may not be empty.")
return attrs
class Meta:
model = NotificationTransport

View File

@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional, Type, Union
from uuid import uuid4
from django.conf import settings
from django.core.validators import URLValidator
from django.db import models
from django.http import HttpRequest
from django.http.request import QueryDict
@ -223,7 +224,7 @@ class NotificationTransport(models.Model):
name = models.TextField(unique=True)
mode = models.TextField(choices=TransportMode.choices)
webhook_url = models.TextField(blank=True)
webhook_url = models.TextField(blank=True, validators=[URLValidator()])
webhook_mapping = models.ForeignKey(
"NotificationWebhookMapping", on_delete=models.SET_DEFAULT, null=True, default=None
)

View File

@ -3,7 +3,6 @@ from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from timeit import default_timer
from traceback import format_tb
from typing import Any, Optional
from celery import Task
@ -42,8 +41,7 @@ class TaskResult:
def with_error(self, exc: Exception) -> "TaskResult":
"""Since errors might not always be pickle-able, set the traceback"""
self.messages.extend(format_tb(exc.__traceback__))
self.messages.append(str(exc))
self.messages.extend(exception_to_string(exc).splitlines())
return self

View File

@ -4,7 +4,13 @@ from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.events.models import Event, EventAction, Notification, NotificationSeverity
from authentik.events.models import (
Event,
EventAction,
Notification,
NotificationSeverity,
TransportMode,
)
class TestEventsAPI(APITestCase):
@ -41,3 +47,23 @@ class TestEventsAPI(APITestCase):
)
notification.refresh_from_db()
self.assertTrue(notification.seen)
def test_transport(self):
"""Test transport API"""
response = self.client.post(
reverse("authentik_api:notificationtransport-list"),
data={
"name": "foo-with",
"mode": TransportMode.WEBHOOK,
"webhook_url": "http://foo.com",
},
)
self.assertEqual(response.status_code, 201)
response = self.client.post(
reverse("authentik_api:notificationtransport-list"),
data={
"name": "foo-without",
"mode": TransportMode.WEBHOOK,
},
)
self.assertEqual(response.status_code, 400)

View File

@ -77,7 +77,7 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
final_dict = {}
for key, value in source.items():
if is_dataclass(value):
# Because asdict calls `copy.deepcopy(obj)` on everything thats not tuple/dict,
# Because asdict calls `copy.deepcopy(obj)` on everything that's not tuple/dict,
# and deepcopy doesn't work with HttpRequests (neither django nor rest_framework).
# Currently, the only dataclass that actually holds an http request is a PolicyRequest
if isinstance(value, PolicyRequest):

View File

@ -57,11 +57,11 @@ class FlowPlan:
markers: list[StageMarker] = field(default_factory=list)
def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
"""Append `stage` to all stages, optionally with stage marker"""
return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
"""Append `stage` to all stages, optionally with stage marker"""
self.bindings.append(binding)
self.markers.append(marker or StageMarker())

View File

@ -438,7 +438,7 @@ class TestFlowExecutor(APITestCase):
# third request, this should trigger the re-evaluate
# A get request will evaluate the policies and this will return stage 4
# but it won't save it, hence we cant' check the plan
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(

View File

@ -11,7 +11,7 @@ from authentik.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and covert it to a normal dict"""
"""Get object's attributes via their serializer, and convert it to a normal dict"""
data = dict(obj.serializer(obj).data)
to_remove = (
"policies",

View File

@ -14,12 +14,7 @@ from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
PolymorphicProxySerializer,
extend_schema,
)
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from sentry_sdk import capture_exception
@ -131,12 +126,12 @@ class FlowExecutorView(APIView):
# pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session
# Early check if there's an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning(
"f(exec): Found existing plan for other flow, deleteing plan",
"f(exec): Found existing plan for other flow, deleting plan",
)
# Existing plan is deleted from session and instance
self.plan = None
@ -213,9 +208,6 @@ class FlowExecutorView(APIView):
serializers=challenge_types(),
resource_type_field_name="component",
),
404: OpenApiResponse(
description="No Token found"
), # This error can be raised by the email stage
},
request=OpenApiTypes.NONE,
parameters=[
@ -441,7 +433,7 @@ class ToDefaultFlow(View):
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
if plan.flow_pk != flow.pk.hex:
LOGGER.warning(
"f(def): Found existing plan for other flow, deleteing plan",
"f(def): Found existing plan for other flow, deleting plan",
flow_slug=flow.slug,
)
del self.request.session[SESSION_KEY_PLAN]

View File

@ -93,6 +93,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
if "exc_info" in hint:
_, exc_value, _ = hint["exc_info"]
if isinstance(exc_value, ignored_classes):
LOGGER.debug("dropping exception", exception=exc_value)
return None
if "logger" in event:
if event["logger"] in [

View File

@ -32,7 +32,7 @@ class TestConfig(TestCase):
config = ConfigLoader()
environ["foo"] = "bar"
self.assertEqual(config.parse_uri("env://foo"), "bar")
self.assertEqual(config.parse_uri("env://fo?bar"), "bar")
self.assertEqual(config.parse_uri("env://foo?bar"), "bar")
def test_uri_file(self):
"""Test URI parsing (file load)"""

View File

@ -27,7 +27,7 @@ class TestHTTP(TestCase):
token = Token.objects.create(
identifier="test", user=self.user, intent=TokenIntents.INTENT_API
)
# Invalid, non-existant token
# Invalid, non-existent token
request = self.factory.get(
"/",
**{
@ -36,7 +36,7 @@ class TestHTTP(TestCase):
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Invalid, user doesn't have permisions
# Invalid, user doesn't have permissions
request = self.factory.get(
"/",
**{

View File

@ -1,5 +1,6 @@
"""authentik lib reflection utilities"""
from importlib import import_module
from typing import Union
from django.conf import settings
@ -19,12 +20,12 @@ def all_subclasses(cls, sort=True):
return classes
def class_to_path(cls):
def class_to_path(cls: type) -> str:
"""Turn Class (Class or instance) into module path"""
return f"{cls.__module__}.{cls.__name__}"
def path_to_class(path):
def path_to_class(path: Union[str, None]) -> Union[type, None]:
"""Import module and return class"""
if not path:
return None

View File

@ -104,7 +104,7 @@ class OutpostConsumer(AuthJsonConsumer):
expected=self.outpost.config.kubernetes_replicas,
).inc()
LOGGER.debug(
"added outpost instace to cache",
"added outpost instance to cache",
outpost=self.outpost,
instance_uuid=self.last_uid,
)

View File

@ -38,6 +38,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(),
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure).lower(),
"AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
}
def _comp_env(self, container: Container) -> bool:
@ -98,15 +99,16 @@ class DockerController(BaseController):
"image": image_name,
"name": container_name,
"detach": True,
"ports": {
f"{port.inner_port or port.port}/{port.protocol.lower()}": port.port
for port in self.deployment_ports
},
"environment": self._get_env(),
"labels": self._get_labels(),
"restart_policy": {"Name": "unless-stopped"},
"network": self.outpost.config.docker_network,
}
if self.outpost.config.docker_map_ports:
container_args["ports"] = {
f"{port.inner_port or port.port}/{port.protocol.lower()}": str(port.port)
for port in self.deployment_ports
}
if settings.TEST:
del container_args["ports"]
del container_args["network"]
@ -164,11 +166,9 @@ class DockerController(BaseController):
self.down()
return self.up(depth + 1)
# Check that container is healthy
if (
container.status == "running"
and container.attrs.get("State", {}).get("Health", {}).get("Status", "")
!= "healthy"
):
if container.status == "running" and container.attrs.get("State", {}).get(
"Health", {}
).get("Status", "") not in ["healthy", "starting"]:
# At this point we know the config is correct, but the container isn't healthy,
# so we just restart it with the same config
if has_been_created:
@ -217,6 +217,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host,
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure),
"AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
},
"labels": self._get_labels(),
}

View File

@ -109,7 +109,7 @@ class KubernetesObjectReconciler(Generic[T]):
except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 404:
self.logger.debug("Failed to get current, assuming non-existant")
self.logger.debug("Failed to get current, assuming non-existent")
return
self.logger.debug("Other unhandled error", exc=exc)
raise exc
@ -129,7 +129,7 @@ class KubernetesObjectReconciler(Generic[T]):
raise NotImplementedError
def retrieve(self) -> T:
"""API Wrapper to retrive object"""
"""API Wrapper to retrieve object"""
raise NotImplementedError
def delete(self, reference: T):

View File

@ -89,6 +89,15 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
)
),
),
V1EnvVar(
name="AUTHENTIK_HOST_BROWSER",
value_from=V1EnvVarSource(
secret_key_ref=V1SecretKeySelector(
name=self.name,
key="authentik_host_browser",
)
),
),
V1EnvVar(
name="AUTHENTIK_TOKEN",
value_from=V1EnvVarSource(

View File

@ -26,7 +26,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
def reconcile(self, current: V1Secret, reference: V1Secret):
super().reconcile(current, reference)
for key in reference.data.keys():
if current.data[key] != reference.data[key]:
if key not in current.data or current.data[key] != reference.data[key]:
raise NeedsUpdate()
def get_reference_object(self) -> V1Secret:
@ -40,6 +40,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
str(self.controller.outpost.config.authentik_host_insecure)
),
"token": b64string(self.controller.outpost.token.key),
"authentik_host_browser": b64string(
self.controller.outpost.config.authentik_host_browser
),
},
)

View File

@ -19,12 +19,16 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
self.api = CoreV1Api(controller.client)
def reconcile(self, current: V1Service, reference: V1Service):
super().reconcile(current, reference)
if len(current.spec.ports) != len(reference.spec.ports):
raise NeedsRecreate()
for port in reference.spec.ports:
if port not in current.spec.ports:
raise NeedsRecreate()
# run the base reconcile last, as that will probably raise NeedsUpdate
# after an authentik update. However the ports might have also changed during
# the update, so this causes the service to be re-created with higher
# priority than being updated.
super().reconcile(current, reference)
def get_reference_object(self) -> V1Service:
"""Get deployment object for outpost"""

View File

@ -30,7 +30,7 @@ class DockerInlineTLS:
return str(path)
def write(self) -> TLSConfig:
"""Create TLSConfig with Certificate Keypairs"""
"""Create TLSConfig with Certificate Key pairs"""
# So yes, this is quite ugly. But sadly, there is no clean way to pass
# docker-py (which is using requests (which is using urllib3)) a certificate
# for verification or authentication as string.

View File

@ -64,6 +64,7 @@ class OutpostConfig:
authentik_host: str = ""
authentik_host_insecure: bool = False
authentik_host_browser: str = ""
log_level: str = CONFIG.y("log_level")
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
@ -71,6 +72,7 @@ class OutpostConfig:
object_naming_template: str = field(default="ak-outpost-%(name)s")
docker_network: Optional[str] = field(default=None)
docker_map_ports: bool = field(default=True)
kubernetes_replicas: int = field(default=1)
kubernetes_namespace: str = field(default_factory=get_namespace)

View File

@ -181,7 +181,7 @@ def outpost_post_save(model_class: str, model_pk: Any):
def outpost_send_update(model_instace: Model):
"""Send outpost update to all registered outposts, irregardless to which authentik
"""Send outpost update to all registered outposts, regardless to which authentik
instance they are connected"""
channel_layer = get_channel_layer()
if isinstance(model_instace, OutpostModel):
@ -208,7 +208,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
@CELERY_APP.task()
def outpost_local_connection():
"""Checks the local environment and create Service connections."""
# Explicitly check against token filename, as thats
# Explicitly check against token filename, as that's
# only present when the integration is enabled
if Path(SERVICE_TOKEN_FILENAME).exists():
LOGGER.debug("Detected in-cluster Kubernetes Config")

View File

@ -3,8 +3,10 @@ from ipaddress import ip_address, ip_network
from typing import TYPE_CHECKING, Optional
from django.http import HttpRequest
from django_otp import devices_for_user
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.flows.planner import PLAN_CONTEXT_SSO
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip
@ -28,6 +30,7 @@ class PolicyEvaluator(BaseEvaluator):
self._messages = []
self._context["ak_logger"] = get_logger(policy_name)
self._context["ak_message"] = self.expr_func_message
self._context["ak_user_has_authenticator"] = self.expr_func_user_has_authenticator
self._context["ip_address"] = ip_address
self._context["ip_network"] = ip_network
self._filename = policy_name or "PolicyEvaluator"
@ -36,6 +39,19 @@ class PolicyEvaluator(BaseEvaluator):
"""Wrapper to append to messages list, which is returned with PolicyResult"""
self._messages.append(message)
def expr_func_user_has_authenticator(
self, user: User, device_type: Optional[str] = None
) -> bool:
"""Check if a user has any authenticator devices, optionally matching *device_type*"""
user_devices = devices_for_user(user)
if device_type:
for device in user_devices:
device_class = device.__class__.__name__.lower().replace("device", "")
if device_class == device_type:
return True
return False
return len(user_devices) > 0
def set_policy_request(self, request: PolicyRequest):
"""Update context based on policy request (if http request is given, update that too)"""
# update website/docs/expressions/_objects.md

View File

@ -46,7 +46,7 @@ def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:
class PolicyProcess(PROCESS_CLASS):
"""Evaluate a single policy within a seprate process"""
"""Evaluate a single policy within a separate process"""
connection: Connection
binding: PolicyBinding

View File

@ -34,7 +34,7 @@ def update_score(request: HttpRequest, username: str, amount: int):
@receiver(user_login_failed)
# pylint: disable=unused-argument
def handle_failed_login(sender, request, credentials, **_):
"""Lower Score for failed loging attempts"""
"""Lower Score for failed login attempts"""
if "username" in credentials:
update_score(request, credentials.get("username"), -1)

View File

@ -14,7 +14,7 @@ from authentik.policies.types import PolicyRequest
def clear_policy_cache():
"""Ensure no policy-related keys are stil cached"""
"""Ensure no policy-related keys are still cached"""
keys = cache.keys("policy_*")
cache.delete(keys)

View File

@ -1,16 +1,15 @@
"""LDAP Provider Docker Contoller"""
"""LDAP Provider Docker Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.models import DockerServiceConnection, Outpost
class LDAPDockerController(DockerController):
"""LDAP Provider Docker Contoller"""
"""LDAP Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
DeploymentPort(9300, "http-metrics", "tcp", 9300),
]

View File

@ -1,11 +1,11 @@
"""LDAP Provider Kubernetes Contoller"""
"""LDAP Provider Kubernetes Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost
class LDAPKubernetesController(KubernetesController):
"""LDAP Provider Kubernetes Contoller"""
"""LDAP Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection)

View File

@ -153,7 +153,7 @@ def protected_resource_view(scopes: list[str]):
if not set(scopes).issubset(set(token.scope)):
LOGGER.warning(
"Scope missmatch.",
"Scope mismatch.",
required=set(scopes),
token_has=set(token.scope),
)

View File

@ -33,7 +33,7 @@ class UserInfoView(View):
for scope in ScopeMapping.objects.filter(scope_name__in=scopes).order_by("scope_name"):
if scope.description != "":
scope_descriptions.append({"id": scope.scope_name, "name": scope.description})
# GitHub Compatibility Scopes are handeled differently, since they required custom paths
# GitHub Compatibility Scopes are handled differently, since they required custom paths
# Hence they don't exist as Scope objects
github_scope_map = {
SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"),

View File

@ -1,5 +1,5 @@
"""ProxyProvider API Views"""
from typing import Any
from typing import Any, Optional
from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError
@ -10,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
@ -106,6 +107,16 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"""Proxy provider serializer for outposts"""
oidc_configuration = SerializerMethodField()
token_validity = SerializerMethodField()
@extend_schema_field(OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information"""
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
def get_token_validity(self, obj: ProxyProvider) -> Optional[float]:
"""Get token validity as second count"""
return timedelta_from_string(obj.token_validity).total_seconds()
class Meta:
@ -127,13 +138,9 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"basic_auth_user_attribute",
"mode",
"cookie_domain",
"token_validity",
]
@extend_schema_field(OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information"""
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
class ProxyOutpostConfigViewSet(ReadOnlyModelViewSet):
"""ProxyProvider Viewset"""

View File

@ -1,4 +1,4 @@
"""Proxy Provider Docker Contoller"""
"""Proxy Provider Docker Controller"""
from urllib.parse import urlparse
from authentik.outposts.controllers.base import DeploymentPort
@ -8,13 +8,12 @@ from authentik.providers.proxy.models import ProxyProvider
class ProxyDockerController(DockerController):
"""Proxy Provider Docker Contoller"""
"""Proxy Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(9000, "http", "tcp"),
DeploymentPort(9300, "http-metrics", "tcp"),
DeploymentPort(9443, "https", "tcp"),
]
@ -30,6 +29,11 @@ class ProxyDockerController(DockerController):
labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/"
labels[
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
] = "/akprox/ping"
labels[
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
] = "9300"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "9000"
return labels

View File

@ -1,4 +1,4 @@
"""Proxy Provider Kubernetes Contoller"""
"""Proxy Provider Kubernetes Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost
@ -7,7 +7,7 @@ from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareR
class ProxyKubernetesController(KubernetesController):
"""Proxy Provider Kubernetes Contoller"""
"""Proxy Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection)

View File

@ -4,6 +4,7 @@ from getpass import getuser
from django.core.management.base import BaseCommand
from django.urls import reverse
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext as _
from structlog.stdlib import get_logger
@ -42,7 +43,7 @@ class Command(BaseCommand):
user=user,
intent=TokenIntents.INTENT_RECOVERY,
description=f"Recovery Token generated by {getuser()} on {_now}",
identifier=f"ak-recovery-{user}-{_now}",
identifier=slugify(f"ak-recovery-{user}-{_now}"),
)
self.stdout.write(
(f"Store this link safely, as it will allow" f" anyone to access authentik as {user}.")

View File

@ -3,10 +3,20 @@ import os
from logging.config import dictConfig
from celery import Celery
from celery.signals import after_task_publish, setup_logging, task_postrun, task_prerun
from celery.signals import (
after_task_publish,
setup_logging,
task_failure,
task_internal_error,
task_postrun,
task_prerun,
)
from django.conf import settings
from structlog.stdlib import get_logger
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
@ -43,6 +53,18 @@ def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
LOGGER.debug("Task finished", task_id=task_id, task_name=task.__name__, state=state)
# pylint: disable=unused-argument
@task_failure.connect
@task_internal_error.connect
def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
"""Create system event for failed task"""
from authentik.events.models import Event, EventAction
LOGGER.warning("Task failure", exception=exception)
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys

View File

@ -529,7 +529,7 @@ for _app in INSTALLED_APPS:
if "apps" in _app:
_app = ".".join(_app.split(".")[:-2])
try:
app_settings = importlib.import_module("%s.settings" % _app)
app_settings = importlib.import_module(f"{_app}.settings")
INSTALLED_APPS.extend(getattr(app_settings, "INSTALLED_APPS", []))
MIDDLEWARE.extend(getattr(app_settings, "MIDDLEWARE", []))
AUTHENTICATION_BACKENDS.extend(getattr(app_settings, "AUTHENTICATION_BACKENDS", []))

View File

@ -1,12 +1,11 @@
"""Source API Views"""
from typing import Any
from django.http.response import Http404
from django.utils.text import slugify
from django_filters.filters import AllValuesMultipleFilter
from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
@ -19,6 +18,9 @@ from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.events.monitored_tasks import TaskInfo
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
class LDAPSourceSerializer(SourceSerializer):
@ -95,19 +97,24 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
responses={
200: TaskSerializer(many=False),
404: OpenApiResponse(description="Task not found"),
200: TaskSerializer(many=True),
}
)
@action(methods=["GET"], detail=True)
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument
def sync_status(self, request: Request, slug: str) -> Response:
"""Get source's sync status"""
source = self.get_object()
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}")
if not task:
raise Http404
return Response(TaskSerializer(task, many=False).data)
results = []
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}-{sync_class.__name__}")
if task:
results.append(task)
return Response(TaskSerializer(results, many=True).data)
class LDAPPropertyMappingSerializer(PropertyMappingSerializer):

View File

@ -119,7 +119,7 @@ class LDAPPasswordChanger:
return True
def ad_password_complexity(self, password: str, user: Optional[User] = None) -> bool:
"""Check if password matches Active direcotry password policies
"""Check if password matches Active directory password policies
https://docs.microsoft.com/en-us/windows/security/threat-protection/
security-policy-settings/password-must-meet-complexity-requirements

View File

@ -4,7 +4,7 @@ from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"sources_ldap_sync": {
"task": "authentik.sources.ldap.tasks.ldap_sync_all",
"schedule": crontab(minute="*/60"), # Run every hour
"schedule": crontab(minute="*/120"), # Run every other hour
"options": {"queue": "authentik_scheduled"},
}
}

View File

@ -11,8 +11,12 @@ from authentik.core.models import User
from authentik.core.signals import password_changed
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.lib.utils.reflection import class_to_path
from authentik.sources.ldap.models import LDAPSource
from authentik.sources.ldap.password import LDAPPasswordChanger
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
from authentik.sources.ldap.tasks import ldap_sync
from authentik.stages.prompt.signals import password_validate
@ -21,8 +25,20 @@ from authentik.stages.prompt.signals import password_validate
# pylint: disable=unused-argument
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
"""Ensure that source is synced on save (if enabled)"""
if instance.enabled:
ldap_sync.delay(instance.pk)
if not instance.enabled:
return
# Don't sync sources when they don't have any property mappings. This will only happen if:
# - the user forgets to set them or
# - the source is newly created, this is the first save event
# and the mappings are created with an m2m event
if not instance.property_mappings.exists() or not instance.property_mappings_group.exists():
return
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
ldap_sync.delay(instance.pk, class_to_path(sync_class))
@receiver(password_validate)
@ -39,7 +55,7 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any],
password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
)
if not passing:
raise ValidationError(_("Password does not match Active Direcory Complexity."))
raise ValidationError(_("Password does not match Active Directory Complexity."))
@receiver(password_changed)

View File

@ -17,11 +17,18 @@ class BaseLDAPSynchronizer:
_source: LDAPSource
_logger: BoundLogger
_messages: list[str]
def __init__(self, source: LDAPSource):
self._source = source
self._messages = []
self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
@property
def messages(self) -> list[str]:
"""Get all UI messages"""
return self._messages
@property
def base_dn_users(self) -> str:
"""Shortcut to get full base_dn for user lookups"""
@ -36,6 +43,11 @@ class BaseLDAPSynchronizer:
return f"{self._source.additional_group_dn},{self._source.base_dn}"
return self._source.base_dn
def message(self, *args, **kwargs):
"""Add message that is later added to the System Task and shown to the user"""
self._messages.append(" ".join(args))
self._logger.warning(*args, **kwargs)
def sync(self) -> int:
"""Sync function, implemented in subclass"""
raise NotImplementedError()

View File

@ -15,7 +15,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
def sync(self) -> int:
"""Iterate over all LDAP Groups and create authentik_core.Group instances"""
if not self._source.sync_groups:
self._logger.warning("Group syncing is disabled for this Source")
self.message("Group syncing is disabled for this Source")
return -1
groups = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_groups,
@ -28,8 +28,8 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
attributes = group.get("attributes", {})
group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn"))))
if self._source.object_uniqueness_field not in attributes:
self._logger.warning(
"Cannot find uniqueness Field in attributes",
self.message(
f"Cannot find uniqueness field in attributes: '{group_dn}",
attributes=attributes.keys(),
dn=group_dn,
)

View File

@ -62,8 +62,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
# group_uniq might be a single string or an array with (hopefully) a single string
if isinstance(group_uniq, list):
if len(group_uniq) < 1:
self._logger.warning(
"Group does not have a uniqueness attribute.",
self.message(
f"Group does not have a uniqueness attribute: '{group_dn}'",
group=group_dn,
)
return None
@ -71,8 +71,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
if group_uniq not in self.group_cache:
groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq})
if not groups.exists():
self._logger.warning(
"Group does not exist in our DB yet, run sync_groups first.",
self.message(
f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
group=group_dn,
)
return None

View File

@ -10,6 +10,7 @@ from pytz import UTC
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
from authentik.sources.ldap.sync.vendor.ad import UserAccountControl
class UserLDAPSynchronizer(BaseLDAPSynchronizer):
@ -18,7 +19,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
def sync(self) -> int:
"""Iterate over all LDAP Users and create authentik_core.User instances"""
if not self._source.sync_users:
self._logger.warning("User syncing is disabled for this Source")
self.message("User syncing is disabled for this Source")
return -1
users = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_users,
@ -31,8 +32,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
attributes = user.get("attributes", {})
user_dn = self._flatten(user.get("entryDN", user.get("dn")))
if self._source.object_uniqueness_field not in attributes:
self._logger.warning(
"Cannot find uniqueness Field in attributes",
self.message(
f"Cannot find uniqueness field in attributes: '{user_dn}",
attributes=attributes.keys(),
dn=user_dn,
)
@ -66,6 +67,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
if created or pwd_last_set >= ak_user.password_change_date:
self.message(f"'{ak_user.username}': Reset user's password")
self._logger.debug(
"Reset user's password",
user=ak_user.username,
@ -74,4 +76,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
)
ak_user.set_unusable_password()
ak_user.save()
if "userAccountControl" in attributes:
uac = UserAccountControl(attributes.get("userAccountControl"))
ak_user.is_active = not uac.ACCOUNTDISABLE
ak_user.save()
return user_count

View File

View File

@ -0,0 +1,32 @@
"""Active Directory specific"""
from enum import IntFlag
class UserAccountControl(IntFlag):
"""UserAccountControl attribute for Active directory users"""
# https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity
# /useraccountcontrol-manipulate-account-properties
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864

View File

@ -1,9 +1,12 @@
"""LDAP Sync tasks"""
from typing import Optional
from django.utils.text import slugify
from ldap3.core.exceptions import LDAPException
from structlog.stdlib import get_logger
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.reflection import class_to_path, path_to_class
from authentik.root.celery import CELERY_APP
from authentik.sources.ldap.models import LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
@ -17,11 +20,19 @@ LOGGER = get_logger()
def ldap_sync_all():
"""Sync all sources"""
for source in LDAPSource.objects.filter(enabled=True):
ldap_sync.delay(source.pk)
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
ldap_sync.delay(source.pk, class_to_path(sync_class))
@CELERY_APP.task(bind=True, base=MonitoredTask)
def ldap_sync(self: MonitoredTask, source_pk: str):
@CELERY_APP.task(
bind=True, base=MonitoredTask, soft_time_limit=60 * 60 * 2, task_time_limit=60 * 60 * 2
)
# TODO: remove Optional[str] in 2021.10
def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: Optional[str] = None):
"""Synchronization of an LDAP Source"""
self.result_timeout_hours = 2
try:
@ -30,17 +41,15 @@ def ldap_sync(self: MonitoredTask, source_pk: str):
# Because the source couldn't be found, we don't have a UID
# to set the state with
return
self.set_uid(slugify(source.name))
if not sync_class:
return
sync = path_to_class(sync_class)
self.set_uid(f"{slugify(source.name)}-{sync.__name__}")
try:
messages = []
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
sync_inst = sync_class(source)
count = sync_inst.sync()
messages.append(f"Synced {count} objects from {sync_class.__name__}")
sync_inst = sync(source)
count = sync_inst.sync()
messages = sync_inst.messages
messages.append(f"Synced {count} objects.")
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,

View File

@ -54,6 +54,7 @@ def mock_ad_connection(password: str) -> Connection:
"objectSid": "user0",
"objectClass": "person",
"distinguishedName": "cn=user0,ou=users,dc=goauthentik,dc=io",
"userAccountControl": 66050,
},
)
# User without SID

View File

@ -72,7 +72,8 @@ class LDAPSyncTests(TestCase):
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)
user_sync.sync()
self.assertTrue(User.objects.filter(username="user0_sn").exists())
user = User.objects.filter(username="user0_sn").first()
self.assertFalse(user.is_active)
self.assertFalse(User.objects.filter(username="user1_sn").exists())
def test_sync_users_openldap(self):

View File

@ -46,9 +46,9 @@ class OAuthSourceSerializer(SourceSerializer):
type = SerializerMethodField()
@extend_schema_field(SourceTypeSerializer)
def get_type(self, instace: OAuthSource) -> SourceTypeSerializer:
def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:
"""Get source's type configuration"""
return SourceTypeSerializer(instace.type).data
return SourceTypeSerializer(instance.type).data
def validate(self, attrs: dict) -> dict:
provider_type = MANAGER.find_type(attrs.get("provider_type", ""))

View File

@ -14,7 +14,7 @@ class Migration(migrations.Migration):
model_name="oauthsource",
name="access_token_url",
field=models.CharField(
help_text="URL used by authentik to retrive tokens.",
help_text="URL used by authentik to retrieve tokens.",
max_length=255,
verbose_name="Access Token URL",
),

View File

@ -15,7 +15,7 @@ class Migration(migrations.Migration):
name="access_token_url",
field=models.CharField(
blank=True,
help_text="URL used by authentik to retrive tokens.",
help_text="URL used by authentik to retrieve tokens.",
max_length=255,
verbose_name="Access Token URL",
),

View File

@ -39,7 +39,7 @@ class Migration(migrations.Migration):
model_name="oauthsource",
name="access_token_url",
field=models.CharField(
help_text="URL used by authentik to retrive tokens.",
help_text="URL used by authentik to retrieve tokens.",
max_length=255,
null=True,
verbose_name="Access Token URL",

View File

@ -37,7 +37,7 @@ class OAuthSource(Source):
max_length=255,
null=True,
verbose_name=_("Access Token URL"),
help_text=_("URL used by authentik to retrive tokens."),
help_text=_("URL used by authentik to retrieve tokens."),
)
profile_url = models.CharField(
max_length=255,

View File

@ -24,7 +24,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="plexsource",
name="plex_token",
field=models.TextField(default="", help_text="Plex token used to check firends"),
field=models.TextField(default="", help_text="Plex token used to check friends"),
),
migrations.AlterField(
model_name="plexsource",

View File

@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="plexsource",
name="plex_token",
field=models.TextField(help_text="Plex token used to check firends"),
field=models.TextField(help_text="Plex token used to check friends"),
),
]

View File

@ -50,7 +50,7 @@ class PlexSource(Source):
default=True,
help_text=_("Allow friends to authenticate, even if you don't share a server."),
)
plex_token = models.TextField(help_text=_("Plex token used to check firends"))
plex_token = models.TextField(help_text=_("Plex token used to check friends"))
@property
def component(self) -> str:

View File

@ -54,7 +54,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
class CaptchaStageView(ChallengeStageView):
"""Simple captcha checker, logic is handeled in django-captcha module"""
"""Simple captcha checker, logic is handled in django-captcha module"""
response_class = CaptchaChallengeResponse

View File

@ -3,9 +3,9 @@ from datetime import timedelta
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext as _
from rest_framework.fields import CharField
@ -22,7 +22,7 @@ from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger()
QS_KEY_TOKEN = "token" # nosec
QS_KEY_TOKEN = "etoken" # nosec
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
@ -65,7 +65,7 @@ class EmailStageView(ChallengeStageView):
) # + 1 because django timesince always rounds down
token_filters = {
"user": pending_user,
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
"identifier": slugify(f"ak-email-stage-{current_stage.name}-{pending_user}"),
}
# Don't check for validity here, we only care if the token exists
tokens = Token.objects.filter(**token_filters)
@ -99,7 +99,10 @@ class EmailStageView(ChallengeStageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify
if QS_KEY_TOKEN in request.session.get(SESSION_KEY_GET, {}):
token = get_object_or_404(Token, key=request.session[SESSION_KEY_GET][QS_KEY_TOKEN])
tokens = Token.filter_not_expired(key=request.session[SESSION_KEY_GET][QS_KEY_TOKEN])
if not tokens.exists():
return self.executor.stage_invalid(_("Invalid token"))
token = tokens.first()
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user
token.delete()
messages.success(request, _("Successfully verified Email."))
@ -118,7 +121,7 @@ class EmailStageView(ChallengeStageView):
challenge = EmailChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"title": "Email sent.",
"title": _("Email sent."),
}
)
return challenge

View File

@ -15,7 +15,8 @@ from authentik.stages.invitation.signals import invitation_used
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
INVITATION_TOKEN_KEY = "token" # nosec
INVITATION_TOKEN_KEY_CONTEXT = "token" # nosec
INVITATION_TOKEN_KEY = "itoken" # nosec
INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
@ -29,10 +30,14 @@ class InvitationStageView(StageView):
def get_token(self) -> Optional[str]:
"""Get token from saved get-arguments or prompt_data"""
# Check for ?token= and ?itoken=
if INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY_CONTEXT in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY_CONTEXT]
# Check for {'token': ''} in the context
if INVITATION_TOKEN_KEY_CONTEXT in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY_CONTEXT]
return None
def get(self, request: HttpRequest) -> HttpResponse:

View File

@ -15,7 +15,11 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT
from authentik.stages.invitation.stage import (
INVITATION_TOKEN_KEY,
INVITATION_TOKEN_KEY_CONTEXT,
PLAN_CONTEXT_PROMPT,
)
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -131,7 +135,7 @@ class TestUserLoginStage(APITestCase):
)
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex}
plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY_CONTEXT: invite.pk.hex}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View File

@ -53,9 +53,11 @@ class PromptChallengeResponse(ChallengeResponse):
def __init__(self, *args, **kwargs):
stage: PromptStage = kwargs.pop("stage", None)
plan: FlowPlan = kwargs.pop("plan", None)
request: HttpRequest = kwargs.pop("request", None)
super().__init__(*args, **kwargs)
self.stage = stage
self.plan = plan
self.request = request
if not self.stage:
return
# list() is called so we only load the fields once
@ -104,8 +106,9 @@ class PromptChallengeResponse(ChallengeResponse):
self._validate_password_fields(*[field.field_key for field in password_fields])
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
engine = ListPolicyEngine(self.stage.validation_policies.all(), user)
engine.request.context = attrs
engine = ListPolicyEngine(self.stage.validation_policies.all(), user, self.request)
engine.request.context[PLAN_CONTEXT_PROMPT] = attrs
engine.request.context.update(attrs)
engine.build()
result = engine.result
if not result.passing:
@ -173,6 +176,7 @@ class PromptStageView(ChallengeStageView):
return PromptChallengeResponse(
instance=None,
data=data,
request=self.request,
stage=self.executor.current_stage,
plan=self.executor.plan,
)

View File

@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.4}
restart: unless-stopped
command: server
environment:
@ -44,7 +44,7 @@ services:
- "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1-rc2}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.4}
restart: unless-stopped
command: worker
networks:

2
go.mod
View File

@ -34,7 +34,7 @@ require (
github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1
go.mongodb.org/mongo-driver v1.5.2 // indirect
goauthentik.io/api v0.0.0-20210913161416-2242c65afb14
goauthentik.io/api v0.202192.5
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558

4
go.sum
View File

@ -554,8 +554,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
goauthentik.io/api v0.0.0-20210913161416-2242c65afb14 h1:sOyZZNbhj6LquWGcGfw0muSbGJcAqRkcvIaGPJkB9I0=
goauthentik.io/api v0.0.0-20210913161416-2242c65afb14/go.mod h1:SPObiI/v8m5cjhj+bGvzb4Nm1w5gmlil5zHQx10sfjE=
goauthentik.io/api v0.202192.5 h1:BS4E71K2uZXy1vAdGVFLJJU0KwvAkkqKg42cYv46ud0=
goauthentik.io/api v0.202192.5/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -17,4 +17,4 @@ func OutpostUserAgent() string {
return fmt.Sprintf("authentik-outpost@%s (build=%s)", VERSION, BUILD())
}
const VERSION = "2021.9.1-rc2"
const VERSION = "2021.9.4"

View File

@ -116,10 +116,9 @@ func (a *APIController) OnRefresh() error {
log.WithError(err).Error("Failed to fetch outpost configuration")
return err
}
outpost := outposts.Results[0]
doGlobalSetup(outpost.Config)
a.Outpost = outposts.Results[0]
log.WithField("name", outpost.Name).Debug("Fetched outpost configuration")
log.WithField("name", a.Outpost.Name).Debug("Fetched outpost configuration")
return a.Server.Refresh()
}

View File

@ -128,6 +128,7 @@ func (ac *APIController) startWSHealth() {
if err != nil {
ac.logger.WithField("loop", "ws-health").WithError(err).Warning("ws write error, reconnecting")
ac.wsConn.CloseAndReconnect()
time.Sleep(time.Second * 5)
continue
} else {
ConnectionStatus.With(prometheus.Labels{

View File

@ -79,6 +79,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
}
if req.SearchRequest.Scope == ldap.ScopeBaseObject {
pi.log.Debug("base scope, showing domain info")
return pi.SearchBase(req, flags.CanSearch)
}
if !flags.CanSearch {
pi.log.Debug("User can't search, showing info about user")
return pi.SearchMe(req, flags)
@ -111,6 +116,12 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
"client": utils.GetIP(req.conn.RemoteAddr()),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
case "groupOfUniqueNames":
fallthrough
case "goauthentik.io/ldap/group":
fallthrough
case "goauthentik.io/ldap/virtual-group":
fallthrough
case GroupObjectClass:
wg := sync.WaitGroup{}
wg.Add(2)
@ -160,7 +171,15 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}()
wg.Wait()
entries = append(gEntries, uEntries...)
case UserObjectClass, "":
case "":
fallthrough
case "organizationalPerson":
fallthrough
case "inetOrgPerson":
fallthrough
case "goauthentik.io/ldap/user":
fallthrough
case UserObjectClass:
uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
searchReq, skip := parseFilterForUser(c.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter, false)
if skip {
@ -181,99 +200,45 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}
func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{u.Username},
},
{
Name: "sAMAccountName",
Values: []string{u.Username},
},
{
Name: "uid",
Values: []string{u.Uid},
},
{
Name: "name",
Values: []string{u.Name},
},
{
Name: "displayName",
Values: []string{u.Name},
},
{
Name: "mail",
Values: []string{*u.Email},
},
{
Name: "objectClass",
Values: []string{UserObjectClass, "organizationalPerson", "goauthentik.io/ldap/user"},
},
{
Name: "uidNumber",
Values: []string{pi.GetUidNumber(u)},
},
{
Name: "gidNumber",
Values: []string{pi.GetUidNumber(u)},
},
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)})
// Old fields for backwards compatibility
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{BoolToString(*u.IsActive)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{BoolToString(u.IsSuperuser)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "goauthentik.io/ldap/active", Values: []string{BoolToString(*u.IsActive)}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "goauthentik.io/ldap/superuser", Values: []string{BoolToString(u.IsSuperuser)}})
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...)
dn := pi.GetUserDN(u.Username)
attrs := AKAttrsToLDAP(u.Attributes)
attrs = pi.ensureAttributes(attrs, map[string][]string{
"memberOf": pi.GroupsForUser(u),
// Old fields for backwards compatibility
"accountStatus": {BoolToString(*u.IsActive)},
"superuser": {BoolToString(u.IsSuperuser)},
"goauthentik.io/ldap/active": {BoolToString(*u.IsActive)},
"goauthentik.io/ldap/superuser": {BoolToString(u.IsSuperuser)},
"cn": {u.Username},
"sAMAccountName": {u.Username},
"uid": {u.Uid},
"name": {u.Name},
"displayName": {u.Name},
"mail": {*u.Email},
"objectClass": {UserObjectClass, "organizationalPerson", "inetOrgPerson", "goauthentik.io/ldap/user"},
"uidNumber": {pi.GetUidNumber(u)},
"gidNumber": {pi.GetUidNumber(u)},
})
return &ldap.Entry{DN: dn, Attributes: attrs}
}
func (pi *ProviderInstance) GroupEntry(g LDAPGroup) *ldap.Entry {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{g.cn},
},
{
Name: "uid",
Values: []string{g.uid},
},
{
Name: "sAMAccountName",
Values: []string{g.cn},
},
{
Name: "gidNumber",
Values: []string{g.gidNumber},
},
}
attrs := AKAttrsToLDAP(g.akAttributes)
objectClass := []string{GroupObjectClass, "groupOfUniqueNames", "goauthentik.io/ldap/group"}
if g.isVirtualGroup {
attrs = append(attrs, &ldap.EntryAttribute{
Name: "objectClass",
Values: []string{GroupObjectClass, "goauthentik.io/ldap/group", "goauthentik.io/ldap/virtual-group"},
})
} else {
attrs = append(attrs, &ldap.EntryAttribute{
Name: "objectClass",
Values: []string{GroupObjectClass, "goauthentik.io/ldap/group"},
})
}
attrs = append(attrs, &ldap.EntryAttribute{Name: "member", Values: g.member})
attrs = append(attrs, &ldap.EntryAttribute{Name: "goauthentik.io/ldap/superuser", Values: []string{BoolToString(g.isSuperuser)}})
if g.akAttributes != nil {
attrs = append(attrs, AKAttrsToLDAP(g.akAttributes)...)
objectClass = append(objectClass, "goauthentik.io/ldap/virtual-group")
}
attrs = pi.ensureAttributes(attrs, map[string][]string{
"objectClass": objectClass,
"member": g.member,
"goauthentik.io/ldap/superuser": {BoolToString(g.isSuperuser)},
"cn": {g.cn},
"uid": {g.uid},
"sAMAccountName": {g.cn},
"gidNumber": {g.gidNumber},
})
return &ldap.Entry{DN: g.dn, Attributes: attrs}
}

View File

@ -0,0 +1,53 @@
package ldap
import (
"fmt"
"github.com/nmcclain/ldap"
"goauthentik.io/internal/constants"
)
func (pi *ProviderInstance) SearchBase(req SearchRequest, authz bool) (ldap.ServerSearchResult, error) {
dn := ""
if authz {
dn = req.SearchRequest.BaseDN
}
return ldap.ServerSearchResult{
Entries: []*ldap.Entry{
{
DN: dn,
Attributes: []*ldap.EntryAttribute{
{
Name: "distinguishedName",
Values: []string{pi.BaseDN},
},
{
Name: "objectClass",
Values: []string{"top", "domain"},
},
{
Name: "supportedLDAPVersion",
Values: []string{"3"},
},
{
Name: "namingContexts",
Values: []string{
pi.BaseDN,
pi.GroupDN,
pi.UserDN,
},
},
{
Name: "vendorName",
Values: []string{"goauthentik.io"},
},
{
Name: "vendorVersion",
Values: []string{fmt.Sprintf("authentik LDAP Outpost Version %s (build %s)", constants.VERSION, constants.BUILD())},
},
},
},
},
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
}, nil
}

View File

@ -38,7 +38,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
SearchRequest: searchReq,
BindDN: bindDN,
conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("scope", ldap.ScopeMap[searchReq.Scope]).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
id: rid,
ctx: span.Context(),
}
@ -60,7 +60,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
if err == nil {
return
}
log.WithError(err.(error)).Error("recover in serach request")
log.WithError(err.(error)).Error("recover in search request")
sentry.CaptureException(err.(error))
}()
@ -74,7 +74,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
}
for _, provider := range ls.providers {
providerBase, _ := goldap.ParseDN(provider.BaseDN)
if providerBase.AncestorOf(bd) {
if providerBase.AncestorOf(bd) || providerBase.Equal(bd) {
return provider.Search(req)
}
}

View File

@ -39,6 +39,9 @@ func ldapResolveTypeSingle(in interface{}) *string {
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
attrList := []*ldap.EntryAttribute{}
if attrs == nil {
return attrList
}
a := attrs.(*map[string]interface{})
for attrKey, attrValue := range *a {
entry := &ldap.EntryAttribute{Name: attrKey}
@ -140,3 +143,26 @@ func (pi *ProviderInstance) GetRIDForGroup(uid string) int32 {
return int32(gid)
}
func (pi *ProviderInstance) ensureAttributes(attrs []*ldap.EntryAttribute, shouldHave map[string][]string) []*ldap.EntryAttribute {
for name, values := range shouldHave {
attrs = pi.mustHaveAttribute(attrs, name, values)
}
return attrs
}
func (pi *ProviderInstance) mustHaveAttribute(attrs []*ldap.EntryAttribute, name string, value []string) []*ldap.EntryAttribute {
shouldSet := true
for _, attr := range attrs {
if attr.Name == name {
shouldSet = false
}
}
if shouldSet {
return append(attrs, &ldap.EntryAttribute{
Name: name,
Values: value,
})
}
return attrs
}

View File

@ -18,7 +18,7 @@ type OIDCEndpoint struct {
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoint {
authUrl := p.OidcConfiguration.AuthorizationEndpoint
endUrl := p.OidcConfiguration.EndSessionEndpoint
if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found {
if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found && browserHost != "" {
host := os.Getenv("AUTHENTIK_HOST")
authUrl = strings.ReplaceAll(authUrl, host, browserHost)
endUrl = strings.ReplaceAll(endUrl, host, browserHost)

View File

@ -3,6 +3,7 @@ package application
import (
"fmt"
"net/http"
"net/url"
"goauthentik.io/api"
"goauthentik.io/internal/outpost/proxyv2/constants"
@ -38,7 +39,8 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_SINGLE {
host = web.GetHost(r)
} else if *a.proxyConfig.Mode == api.PROXYMODE_FORWARD_DOMAIN {
host = a.proxyConfig.ExternalHost
eh, _ := url.Parse(a.proxyConfig.ExternalHost)
host = eh.Host
}
// set the redirect flag to the current URL we have, since we redirect
// to a (possibly) different domain, but we want to be redirected back

View File

@ -18,12 +18,22 @@ func GetStore(p api.ProxyOutpostConfig) sessions.Store {
if err != nil {
panic(err)
}
if p.TokenValidity.IsSet() {
t := p.TokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length
rs.Options.MaxAge = int(*t) + 1
}
rs.Options.Domain = *p.CookieDomain
log.Info("using redis session backend")
store = rs
} else {
cs := sessions.NewCookieStore([]byte(*p.CookieSecret))
cs.Options.Domain = *p.CookieDomain
if p.TokenValidity.IsSet() {
t := p.TokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length
cs.Options.MaxAge = int(*t) + 1
}
log.Info("using cookie session backend")
store = cs
}

View File

@ -45,6 +45,15 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
host := web.GetHost(r)
a, ok := ps.apps[host]
if !ok {
// If we only have one handler, host name switching doesn't matter
if len(ps.apps) == 1 {
ps.log.WithField("host", host).Warning("passing to single app mux")
for k := range ps.apps {
ps.apps[k].ServeHTTP(rw, r)
return
}
}
ps.log.WithField("host", host).Warning("no app for hostname")
rw.WriteHeader(400)
return

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