Compare commits

...

112 Commits

Author SHA1 Message Date
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
338 changed files with 3951 additions and 2650 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2021.9.1-rc3 current_version = 2021.9.3
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) 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

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

View File

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

View File

@ -20,6 +20,11 @@ test:
lint-fix: lint-fix:
isort authentik tests lifecycle isort authentik tests lifecycle
black 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: lint:
pyright authentik tests lifecycle pyright authentik tests lifecycle

View File

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

236
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "19d5324fd1a4af125ed57a683030ca14ee2d3648117748e4b32656875484728e" "sha256": "babb6061c555f8f239f00210b2a0356763bdaaca2f3d704cf3444891b84db84d"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -120,19 +120,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:9b6679e3c54f8c32c09872948450ece87473cbc830cfd6c84dad58eb014329ba", "sha256:7b45b224442c479de4bc6e6e9cb0557b642fc7a77edc8702e393ccaa2e0aa128",
"sha256:caa96b7c2be2168b6efc25ab1fb61c996174bcfbcab21b5f642608185daa6403" "sha256:c388da7dc1a596755f39de990a72e05cee558d098e81de63de55bd9598cc5134"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.18.43" "version": "==1.18.48"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:b74d0a5fe0f7b73fa4b5670eaa9ff456b0bce70966d478dcd631c91458916eb6", "sha256:2c25a76f09223b2f00ad578df34492b7b84cd4828fc90c08ccbdd1d70abbd7eb",
"sha256:de7bf9c9098578d386b785b5b6eab954acccd3f79fe3e2eb971da608c967082b" "sha256:9d5b70be2f417d0aa30788049fd20473ad27218eccd05e71f545b4b4e09c79a0"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.21.43" "version": "==1.21.48"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -252,11 +252,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367", "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd" "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
], ],
"markers": "python_version >= '3'", "markers": "python_version >= '3'",
"version": "==2.0.5" "version": "==2.0.6"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -286,6 +286,14 @@
], ],
"version": "==0.2.0" "version": "==0.2.0"
}, },
"codespell": {
"hashes": [
"sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5",
"sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"
],
"index": "pypi",
"version": "==2.1.0"
},
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
@ -308,8 +316,10 @@
"sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
"sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
"sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
"sha256:3c4129fc3fdc0fa8e40861b5ac0c673315b3c902bbdc05fc176764815b43dd1d",
"sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
"sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
"sha256:695104a9223a7239d155d7627ad912953b540929ef97ae0c34c7b8bf30857e89",
"sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
"sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
"sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
@ -369,11 +379,11 @@
}, },
"django-filter": { "django-filter": {
"hashes": [ "hashes": [
"sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06", "sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e",
"sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1" "sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.4.0" "version": "==21.1"
}, },
"django-guardian": { "django-guardian": {
"hashes": [ "hashes": [
@ -449,11 +459,11 @@
}, },
"drf-spectacular": { "drf-spectacular": {
"hashes": [ "hashes": [
"sha256:47ef6ec8ff48ac8aede6ec12450a55fee381cf84de969ef1724dcde5a93de6b8", "sha256:65df818226477cdfa629947ea52bc0cc13eb40550b192eeccec64a6b782651fd",
"sha256:d746b936cb4cddec380ea95bf91de6a6721777dfc42e0eea53b83c61a625e94e" "sha256:f71205da3645d770545abeaf48e8a15afd6ee9a76e57c03df4592e51be1059bf"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.18.2" "version": "==0.19.0"
}, },
"duo-client": { "duo-client": {
"hashes": [ "hashes": [
@ -480,11 +490,11 @@
}, },
"geoip2": { "geoip2": {
"hashes": [ "hashes": [
"sha256:906a1dbf15a179a1af3522970e8420ab15bb3e0afc526942cc179e12146d9c1d", "sha256:f150bed3190d543712a17467208388d31bd8ddb49b2226fba53db8aaedb8ba89",
"sha256:b97b44031fdc463e84eb1316b4f19edd978cb1d78703465fcb1e36dc5a822ba6" "sha256:f9172cdfb2a5f9225ace5e30dd7426413ad28798a5f474cd1538780686bd6a87"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.2.0" "version": "==4.4.0"
}, },
"google-auth": { "google-auth": {
"hashes": [ "hashes": [
@ -704,10 +714,10 @@
}, },
"maxminddb": { "maxminddb": {
"hashes": [ "hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" "sha256:e37707ec4fab115804670e0fb7aedb4b57075a8b6f80052bdc648d3c005184e5"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.0.3" "version": "==2.2.0"
}, },
"msgpack": { "msgpack": {
"hashes": [ "hashes": [
@ -898,39 +908,39 @@
}, },
"pycryptodome": { "pycryptodome": {
"hashes": [ "hashes": [
"sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0", "sha256:04e14c732c3693d2830839feed5129286ce47ffa8bfe90e4ae042c773e51c677",
"sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d", "sha256:11d3164fb49fdee000fde05baecce103c0c698168ef1a18d9c7429dd66f0f5bb",
"sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce", "sha256:217dcc0c92503f7dd4b3d3b7d974331a4419f97f555c99a845c3b366fed7056b",
"sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06", "sha256:24c1b7705d19d8ae3e7255431efd2e526006855df62620118dd7b5374c6372f6",
"sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35", "sha256:309529d2526f3fb47102aeef376b3459110a6af7efb162e860b32e3a17a46f06",
"sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27", "sha256:3a153658d97258ca20bf18f7fe31c09cc7c558b6f8974a6ec74e19f6c634bd64",
"sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129", "sha256:3f9fb499e267039262569d08658132c9cd8b136bf1d8c56b72f70ed05551e526",
"sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9", "sha256:3faa6ebd35c61718f3f8862569c1f38450c24f3ededb213e1a64806f02f584bc",
"sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673", "sha256:40083b0d7f277452c7f2dd4841801f058cc12a74c219ee4110d65774c6a58bef",
"sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1", "sha256:49e54f2245befb0193848c8c8031d8d1358ed4af5a1ae8d0a3ba669a5cdd3a72",
"sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6", "sha256:4e8fc4c48365ce8a542fe48bf1360da05bb2851df12f64fc94d751705e7cdbe7",
"sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8", "sha256:54d4e4d45f349d8c4e2f31c2734637ff62a844af391b833f789da88e43a8f338",
"sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c", "sha256:66301e4c42dee43ee2da256625d3fe81ef98cc9924c2bd535008cc3ad8ded77b",
"sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713", "sha256:6b45fcace5a5d9c57ba87cf804b161adc62aa826295ce7f7acbcbdc0df74ed37",
"sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6", "sha256:7efec2418e9746ec48e264eea431f8e422d931f71c57b1c96ee202b117f58fa9",
"sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438", "sha256:851e6d4930b160417235955322db44adbdb19589918670d63f4acd5d92959ac0",
"sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e", "sha256:8e82524e7c354033508891405574d12e612cc4fdd3b55d2c238fc1a3e300b606",
"sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07", "sha256:8ec154ec445412df31acf0096e7f715e30e167c8f2318b8f5b1ab7c28f4c82f7",
"sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6", "sha256:91ba4215a1f37d0f371fe43bc88c5ff49c274849f3868321c889313787de7672",
"sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd", "sha256:97e7df67a4da2e3f60612bbfd6c3f243a63a15d8f4797dd275e1d7b44a65cb12",
"sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6", "sha256:9a2312440057bf29b9582f72f14d79692044e63bfbc4b4bbea8559355f44f3dd",
"sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8", "sha256:a7471646d8cd1a58bb696d667dcb3853e5c9b341b68dcf3c3cc0893d0f98ca5f",
"sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427", "sha256:ac3012c36633564b2b5539bb7c6d9175f31d2ce74844e9abe654c428f02d0fd8",
"sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067", "sha256:b1daf251395af7336ddde6a0015ba5e632c18fe646ba930ef87402537358e3b4",
"sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8", "sha256:b217b4525e60e1af552d62bec01b4685095436d4de5ecde0f05d75b2f95ba6d4",
"sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b", "sha256:c61ea053bd5d4c12a063d7e704fbe1c45abb5d2510dab55bd95d166ba661604f",
"sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa", "sha256:c6469d1453f5864e3321a172b0aa671b938d753cbf2376b99fa2ab8841539bb8",
"sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf", "sha256:cefe6b267b8e5c3c72e11adec35a9c7285b62e8ea141b63e87055e9a9e5f2f8c",
"sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da", "sha256:d713dc0910e5ded07852a05e9b75f1dd9d3a31895eebee0668f612779b2a748c",
"sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7" "sha256:db15fa07d2a4c00beeb5e9acdfdbc1c79f9ccfbdc1a8f36c82c4aa44951b33c9"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.10.1" "version": "==3.10.4"
}, },
"pyjwt": { "pyjwt": {
"hashes": [ "hashes": [
@ -1082,11 +1092,11 @@
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:ebe99144fa9618d4b0e7617e7929b75acd905d258c3c779edcd34c0adfffe26c", "sha256:4297555ddc37c7136740e6b547b7d68f5bca0b4832f94ac097e5d531a4c77528",
"sha256:f33d34c886d0ba24c75ea8885a8b3a172358853c7cbde05979fc99c29ef7bc52" "sha256:ea04bc3be6eb082f34ff3f8f6380ea9c691766592298f3f975a435dafac6bf6a"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.1" "version": "==1.4.1"
}, },
"service-identity": { "service-identity": {
"hashes": [ "hashes": [
@ -1176,11 +1186,11 @@
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.26.6" "version": "==1.26.7"
}, },
"uvicorn": { "uvicorn": {
"extras": [ "extras": [
@ -1457,11 +1467,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367", "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd" "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
], ],
"markers": "python_version >= '3'", "markers": "python_version >= '3'",
"version": "==2.0.5" "version": "==2.0.6"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -1547,11 +1557,11 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b", "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647",
"sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8" "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==3.1.18" "version": "==3.1.24"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@ -1642,11 +1652,11 @@
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.3.0" "version": "==2.4.0"
}, },
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
@ -1748,49 +1758,49 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468", "sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656",
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354", "sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a",
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308", "sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae",
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d", "sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0",
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc", "sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1",
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8", "sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7",
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797", "sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3",
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2", "sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c",
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13", "sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7",
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d", "sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3",
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a", "sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673",
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0", "sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c",
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73", "sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e",
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1", "sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d",
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed", "sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2",
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a", "sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72",
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b", "sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3",
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f", "sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11",
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256", "sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90",
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb", "sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5",
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2", "sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec",
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983", "sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a",
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb", "sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561",
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645", "sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab",
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8", "sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078",
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a", "sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc",
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906", "sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1",
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f", "sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c",
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c", "sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6",
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892", "sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48",
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0", "sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456",
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e", "sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8",
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e", "sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18",
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed", "sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4",
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c", "sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736",
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374", "sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949",
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd", "sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346",
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791", "sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db",
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a", "sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad",
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1", "sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9",
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759" "sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"
], ],
"version": "==2021.8.28" "version": "==2021.9.24"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
@ -1861,11 +1871,11 @@
"secure" "secure"
], ],
"hashes": [ "hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.26.6" "version": "==1.26.7"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [

View File

@ -1,3 +1,3 @@
"""authentik""" """authentik"""
__version__ = "2021.9.1-rc3" __version__ = "2021.9.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -84,7 +84,7 @@ class SystemSerializer(PassiveSerializer):
return now() return now()
def get_embedded_outpost_host(self, request: Request) -> str: 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) outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts.exists(): if not outposts.exists():
return "" return ""

View File

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

View File

@ -11,7 +11,7 @@ from drf_spectacular.types import OpenApiTypes
def build_standard_type(obj, **kwargs): 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 = build_basic_type(obj)
schema.update(kwargs) schema.update(kwargs)
return schema return schema

View File

@ -63,7 +63,7 @@ class ConfigView(APIView):
@extend_schema(responses={200: ConfigSerializer(many=False)}) @extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response: def get(self, request: Request) -> Response:
"""Retrive public configuration options""" """Retrieve public configuration options"""
config = ConfigSerializer( config = ConfigSerializer(
{ {
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"), "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.request import Request
from rest_framework.throttling import AnonRateThrottle from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView from rest_framework.views import APIView
from structlog.stdlib import get_logger
from authentik.api.tasks import sentry_proxy from authentik.api.tasks import sentry_proxy
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
LOGGER = get_logger()
class PlainTextParser(BaseParser): class PlainTextParser(BaseParser):
"""Plain text parser.""" """Plain text parser."""
@ -45,6 +48,7 @@ class SentryTunnelView(APIView):
"""Sentry tunnel, to prevent ad blockers from blocking sentry""" """Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled # Only allow usage of this endpoint when error reporting is enabled
if not CONFIG.y_bool("error_reporting.enabled", False): if not CONFIG.y_bool("error_reporting.enabled", False):
LOGGER.debug("error reporting disabled")
return HttpResponse(status=400) return HttpResponse(status=400)
# Body is 2 json objects separated by \n # Body is 2 json objects separated by \n
full_body = request.body full_body = request.body
@ -55,6 +59,7 @@ class SentryTunnelView(APIView):
# Check that the DSN is what we expect # Check that the DSN is what we expect
dsn = header.get("dsn", "") dsn = header.get("dsn", "")
if dsn != settings.SENTRY_DSN: if dsn != settings.SENTRY_DSN:
LOGGER.debug("Invalid dsn", have=dsn, expected=settings.SENTRY_DSN)
return HttpResponse(status=400) return HttpResponse(status=400)
sentry_proxy.delay(full_body.decode()) sentry_proxy.delay(full_body.decode())
return HttpResponse(status=204) return HttpResponse(status=204)

View File

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

View File

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

View File

@ -26,7 +26,7 @@ class Migration(migrations.Migration):
), ),
( (
"username_link", "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", "username_deny",

View File

@ -283,7 +283,7 @@ class SourceUserMatchingModes(models.TextChoices):
) )
USERNAME_LINK = "username_link", _( 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." "when a username is used with another source."
) )
) )

View File

@ -1,7 +1,10 @@
"""NotificationTransport API Views""" """NotificationTransport API Views"""
from typing import Any
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -29,6 +32,14 @@ class NotificationTransportSerializer(ModelSerializer):
"""Return selected mode with a UI Label""" """Return selected mode with a UI Label"""
return TransportMode(instance.mode).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: class Meta:
model = NotificationTransport model = NotificationTransport

View File

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

View File

@ -4,7 +4,13 @@ from django.urls import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import User 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): class TestEventsAPI(APITestCase):
@ -41,3 +47,23 @@ class TestEventsAPI(APITestCase):
) )
notification.refresh_from_db() notification.refresh_from_db()
self.assertTrue(notification.seen) 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 = {} final_dict = {}
for key, value in source.items(): for key, value in source.items():
if is_dataclass(value): 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). # 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 # Currently, the only dataclass that actually holds an http request is a PolicyRequest
if isinstance(value, PolicyRequest): if isinstance(value, PolicyRequest):

View File

@ -57,11 +57,11 @@ class FlowPlan:
markers: list[StageMarker] = field(default_factory=list) markers: list[StageMarker] = field(default_factory=list)
def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None): 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) return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None): 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.bindings.append(binding)
self.markers.append(marker or StageMarker()) self.markers.append(marker or StageMarker())

View File

@ -438,7 +438,7 @@ class TestFlowExecutor(APITestCase):
# third request, this should trigger the re-evaluate # third request, this should trigger the re-evaluate
# A get request will evaluate the policies and this will return stage 4 # 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) response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(

View File

@ -11,7 +11,7 @@ from authentik.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> dict[str, Any]: 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) data = dict(obj.serializer(obj).data)
to_remove = ( to_remove = (
"policies", "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.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View from django.views.generic import View
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import ( from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
OpenApiParameter,
OpenApiResponse,
PolymorphicProxySerializer,
extend_schema,
)
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.views import APIView from rest_framework.views import APIView
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
@ -131,12 +126,12 @@ class FlowExecutorView(APIView):
# pylint: disable=unused-argument, too-many-return-statements # pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: 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: if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN] self.plan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex: if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning( 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 # Existing plan is deleted from session and instance
self.plan = None self.plan = None
@ -213,9 +208,6 @@ class FlowExecutorView(APIView):
serializers=challenge_types(), serializers=challenge_types(),
resource_type_field_name="component", resource_type_field_name="component",
), ),
404: OpenApiResponse(
description="No Token found"
), # This error can be raised by the email stage
}, },
request=OpenApiTypes.NONE, request=OpenApiTypes.NONE,
parameters=[ parameters=[
@ -441,7 +433,7 @@ class ToDefaultFlow(View):
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN] plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
if plan.flow_pk != flow.pk.hex: if plan.flow_pk != flow.pk.hex:
LOGGER.warning( 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, flow_slug=flow.slug,
) )
del self.request.session[SESSION_KEY_PLAN] del self.request.session[SESSION_KEY_PLAN]

View File

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

View File

@ -27,7 +27,7 @@ class TestHTTP(TestCase):
token = Token.objects.create( token = Token.objects.create(
identifier="test", user=self.user, intent=TokenIntents.INTENT_API identifier="test", user=self.user, intent=TokenIntents.INTENT_API
) )
# Invalid, non-existant token # Invalid, non-existent token
request = self.factory.get( request = self.factory.get(
"/", "/",
**{ **{
@ -36,7 +36,7 @@ class TestHTTP(TestCase):
}, },
) )
self.assertEqual(get_client_ip(request), "127.0.0.1") 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( request = self.factory.get(
"/", "/",
**{ **{

View File

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

View File

@ -38,6 +38,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(), "AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(),
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure).lower(), "AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure).lower(),
"AUTHENTIK_TOKEN": self.outpost.token.key, "AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
} }
def _comp_env(self, container: Container) -> bool: def _comp_env(self, container: Container) -> bool:
@ -164,11 +165,9 @@ class DockerController(BaseController):
self.down() self.down()
return self.up(depth + 1) return self.up(depth + 1)
# Check that container is healthy # Check that container is healthy
if ( if container.status == "running" and container.attrs.get("State", {}).get(
container.status == "running" "Health", {}
and container.attrs.get("State", {}).get("Health", {}).get("Status", "") ).get("Status", "") not in ["healthy", "starting"]:
!= "healthy"
):
# At this point we know the config is correct, but the container isn't healthy, # At this point we know the config is correct, but the container isn't healthy,
# so we just restart it with the same config # so we just restart it with the same config
if has_been_created: if has_been_created:
@ -217,6 +216,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host, "AUTHENTIK_HOST": self.outpost.config.authentik_host,
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure), "AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure),
"AUTHENTIK_TOKEN": self.outpost.token.key, "AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
}, },
"labels": self._get_labels(), "labels": self._get_labels(),
} }

View File

@ -109,7 +109,7 @@ class KubernetesObjectReconciler(Generic[T]):
except (OpenApiException, HTTPError) as exc: except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member # pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 404: 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 return
self.logger.debug("Other unhandled error", exc=exc) self.logger.debug("Other unhandled error", exc=exc)
raise exc raise exc
@ -129,7 +129,7 @@ class KubernetesObjectReconciler(Generic[T]):
raise NotImplementedError raise NotImplementedError
def retrieve(self) -> T: def retrieve(self) -> T:
"""API Wrapper to retrive object""" """API Wrapper to retrieve object"""
raise NotImplementedError raise NotImplementedError
def delete(self, reference: T): 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( V1EnvVar(
name="AUTHENTIK_TOKEN", name="AUTHENTIK_TOKEN",
value_from=V1EnvVarSource( value_from=V1EnvVarSource(

View File

@ -26,7 +26,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
def reconcile(self, current: V1Secret, reference: V1Secret): def reconcile(self, current: V1Secret, reference: V1Secret):
super().reconcile(current, reference) super().reconcile(current, reference)
for key in reference.data.keys(): 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() raise NeedsUpdate()
def get_reference_object(self) -> V1Secret: def get_reference_object(self) -> V1Secret:
@ -40,6 +40,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
str(self.controller.outpost.config.authentik_host_insecure) str(self.controller.outpost.config.authentik_host_insecure)
), ),
"token": b64string(self.controller.outpost.token.key), "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) self.api = CoreV1Api(controller.client)
def reconcile(self, current: V1Service, reference: V1Service): def reconcile(self, current: V1Service, reference: V1Service):
super().reconcile(current, reference)
if len(current.spec.ports) != len(reference.spec.ports): if len(current.spec.ports) != len(reference.spec.ports):
raise NeedsRecreate() raise NeedsRecreate()
for port in reference.spec.ports: for port in reference.spec.ports:
if port not in current.spec.ports: if port not in current.spec.ports:
raise NeedsRecreate() 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: def get_reference_object(self) -> V1Service:
"""Get deployment object for outpost""" """Get deployment object for outpost"""

View File

@ -30,7 +30,7 @@ class DockerInlineTLS:
return str(path) return str(path)
def write(self) -> TLSConfig: 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 # 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 # docker-py (which is using requests (which is using urllib3)) a certificate
# for verification or authentication as string. # for verification or authentication as string.

View File

@ -64,6 +64,7 @@ class OutpostConfig:
authentik_host: str = "" authentik_host: str = ""
authentik_host_insecure: bool = False authentik_host_insecure: bool = False
authentik_host_browser: str = ""
log_level: str = CONFIG.y("log_level") log_level: str = CONFIG.y("log_level")
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled") error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")

View File

@ -181,7 +181,7 @@ def outpost_post_save(model_class: str, model_pk: Any):
def outpost_send_update(model_instace: Model): 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""" instance they are connected"""
channel_layer = get_channel_layer() channel_layer = get_channel_layer()
if isinstance(model_instace, OutpostModel): if isinstance(model_instace, OutpostModel):
@ -208,7 +208,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
@CELERY_APP.task() @CELERY_APP.task()
def outpost_local_connection(): def outpost_local_connection():
"""Checks the local environment and create Service connections.""" """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 # only present when the integration is enabled
if Path(SERVICE_TOKEN_FILENAME).exists(): if Path(SERVICE_TOKEN_FILENAME).exists():
LOGGER.debug("Detected in-cluster Kubernetes Config") 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 typing import TYPE_CHECKING, Optional
from django.http import HttpRequest from django.http import HttpRequest
from django_otp import devices_for_user
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.flows.planner import PLAN_CONTEXT_SSO from authentik.flows.planner import PLAN_CONTEXT_SSO
from authentik.lib.expression.evaluator import BaseEvaluator from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip from authentik.lib.utils.http import get_client_ip
@ -28,6 +30,7 @@ class PolicyEvaluator(BaseEvaluator):
self._messages = [] self._messages = []
self._context["ak_logger"] = get_logger(policy_name) self._context["ak_logger"] = get_logger(policy_name)
self._context["ak_message"] = self.expr_func_message 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_address"] = ip_address
self._context["ip_network"] = ip_network self._context["ip_network"] = ip_network
self._filename = policy_name or "PolicyEvaluator" self._filename = policy_name or "PolicyEvaluator"
@ -36,6 +39,19 @@ class PolicyEvaluator(BaseEvaluator):
"""Wrapper to append to messages list, which is returned with PolicyResult""" """Wrapper to append to messages list, which is returned with PolicyResult"""
self._messages.append(message) 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): def set_policy_request(self, request: PolicyRequest):
"""Update context based on policy request (if http request is given, update that too)""" """Update context based on policy request (if http request is given, update that too)"""
# update website/docs/expressions/_objects.md # update website/docs/expressions/_objects.md

View File

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

View File

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

View File

@ -14,7 +14,7 @@ from authentik.policies.types import PolicyRequest
def clear_policy_cache(): def clear_policy_cache():
"""Ensure no policy-related keys are stil cached""" """Ensure no policy-related keys are still cached"""
keys = cache.keys("policy_*") keys = cache.keys("policy_*")
cache.delete(keys) 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.base import DeploymentPort
from authentik.outposts.controllers.docker import DockerController from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.models import DockerServiceConnection, Outpost from authentik.outposts.models import DockerServiceConnection, Outpost
class LDAPDockerController(DockerController): class LDAPDockerController(DockerController):
"""LDAP Provider Docker Contoller""" """LDAP Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection): def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = [ self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389), DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636), 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.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost from authentik.outposts.models import KubernetesServiceConnection, Outpost
class LDAPKubernetesController(KubernetesController): class LDAPKubernetesController(KubernetesController):
"""LDAP Provider Kubernetes Contoller""" """LDAP Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection): def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection) 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)): if not set(scopes).issubset(set(token.scope)):
LOGGER.warning( LOGGER.warning(
"Scope missmatch.", "Scope mismatch.",
required=set(scopes), required=set(scopes),
token_has=set(token.scope), 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"): for scope in ScopeMapping.objects.filter(scope_name__in=scopes).order_by("scope_name"):
if scope.description != "": if scope.description != "":
scope_descriptions.append({"id": scope.scope_name, "name": 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 # Hence they don't exist as Scope objects
github_scope_map = { github_scope_map = {
SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"), SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"),

View File

@ -1,5 +1,5 @@
"""ProxyProvider API Views""" """ProxyProvider API Views"""
from typing import Any from typing import Any, Optional
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError 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.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer 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.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyMode, ProxyProvider from authentik.providers.proxy.models import ProxyMode, ProxyProvider
@ -106,6 +107,16 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"""Proxy provider serializer for outposts""" """Proxy provider serializer for outposts"""
oidc_configuration = SerializerMethodField() 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: class Meta:
@ -127,13 +138,9 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"basic_auth_user_attribute", "basic_auth_user_attribute",
"mode", "mode",
"cookie_domain", "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): class ProxyOutpostConfigViewSet(ReadOnlyModelViewSet):
"""ProxyProvider Viewset""" """ProxyProvider Viewset"""

View File

@ -1,4 +1,4 @@
"""Proxy Provider Docker Contoller""" """Proxy Provider Docker Controller"""
from urllib.parse import urlparse from urllib.parse import urlparse
from authentik.outposts.controllers.base import DeploymentPort from authentik.outposts.controllers.base import DeploymentPort
@ -8,13 +8,12 @@ from authentik.providers.proxy.models import ProxyProvider
class ProxyDockerController(DockerController): class ProxyDockerController(DockerController):
"""Proxy Provider Docker Contoller""" """Proxy Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection): def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = [ self.deployment_ports = [
DeploymentPort(9000, "http", "tcp"), DeploymentPort(9000, "http", "tcp"),
DeploymentPort(9300, "http-metrics", "tcp"),
DeploymentPort(9443, "https", "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.rule"] = f"Host({','.join(hosts)})"
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true" 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.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" labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "9000"
return labels 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.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost from authentik.outposts.models import KubernetesServiceConnection, Outpost
@ -7,7 +7,7 @@ from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareR
class ProxyKubernetesController(KubernetesController): class ProxyKubernetesController(KubernetesController):
"""Proxy Provider Kubernetes Contoller""" """Proxy Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection): def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection) super().__init__(outpost, connection)

View File

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

View File

@ -119,7 +119,7 @@ class LDAPPasswordChanger:
return True return True
def ad_password_complexity(self, password: str, user: Optional[User] = None) -> bool: 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/ https://docs.microsoft.com/en-us/windows/security/threat-protection/
security-policy-settings/password-must-meet-complexity-requirements security-policy-settings/password-must-meet-complexity-requirements

View File

@ -48,7 +48,7 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any],
password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None) password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
) )
if not passing: if not passing:
raise ValidationError(_("Password does not match Active Direcory Complexity.")) raise ValidationError(_("Password does not match Active Directory Complexity."))
@receiver(password_changed) @receiver(password_changed)

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ class OAuthSource(Source):
max_length=255, max_length=255,
null=True, null=True,
verbose_name=_("Access Token URL"), 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( profile_url = models.CharField(
max_length=255, max_length=255,

View File

@ -24,7 +24,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name="plexsource", model_name="plexsource",
name="plex_token", 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( migrations.AlterField(
model_name="plexsource", model_name="plexsource",

View File

@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name="plexsource", model_name="plexsource",
name="plex_token", 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, default=True,
help_text=_("Allow friends to authenticate, even if you don't share a server."), 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 @property
def component(self) -> str: def component(self) -> str:

View File

@ -54,7 +54,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
class CaptchaStageView(ChallengeStageView): 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 response_class = CaptchaChallengeResponse

View File

@ -3,9 +3,9 @@ from datetime import timedelta
from django.contrib import messages from django.contrib import messages
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.fields import CharField 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 from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger() LOGGER = get_logger()
QS_KEY_TOKEN = "token" # nosec QS_KEY_TOKEN = "etoken" # nosec
PLAN_CONTEXT_EMAIL_SENT = "email_sent" PLAN_CONTEXT_EMAIL_SENT = "email_sent"
@ -65,7 +65,7 @@ class EmailStageView(ChallengeStageView):
) # + 1 because django timesince always rounds down ) # + 1 because django timesince always rounds down
token_filters = { token_filters = {
"user": pending_user, "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 # Don't check for validity here, we only care if the token exists
tokens = Token.objects.filter(**token_filters) tokens = Token.objects.filter(**token_filters)
@ -99,7 +99,10 @@ class EmailStageView(ChallengeStageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify # Check if the user came back from the email link to verify
if QS_KEY_TOKEN in request.session.get(SESSION_KEY_GET, {}): 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 self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user
token.delete() token.delete()
messages.success(request, _("Successfully verified Email.")) messages.success(request, _("Successfully verified Email."))
@ -118,7 +121,7 @@ class EmailStageView(ChallengeStageView):
challenge = EmailChallenge( challenge = EmailChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"title": "Email sent.", "title": _("Email sent."),
} }
) )
return challenge 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 from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger() 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_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation" INVITATION = "invitation"
@ -29,10 +30,14 @@ class InvitationStageView(StageView):
def get_token(self) -> Optional[str]: def get_token(self) -> Optional[str]:
"""Get token from saved get-arguments or prompt_data""" """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, {}): if INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY] return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}): if INVITATION_TOKEN_KEY_CONTEXT in self.request.session.get(SESSION_KEY_GET, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY] 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 return None
def get(self, request: HttpRequest) -> HttpResponse: 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.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage 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 import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND 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 = 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 = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()

View File

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

View File

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

2
go.mod
View File

@ -34,7 +34,7 @@ require (
github.com/recws-org/recws v1.3.1 github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
go.mongodb.org/mongo-driver v1.5.2 // indirect 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/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 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.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/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= 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.202192.5 h1:BS4E71K2uZXy1vAdGVFLJJU0KwvAkkqKg42cYv46ud0=
goauthentik.io/api v0.0.0-20210913161416-2242c65afb14/go.mod h1:SPObiI/v8m5cjhj+bGvzb4Nm1w5gmlil5zHQx10sfjE= 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-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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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()) return fmt.Sprintf("authentik-outpost@%s (build=%s)", VERSION, BUILD())
} }
const VERSION = "2021.9.1-rc3" const VERSION = "2021.9.3"

View File

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

View File

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

View File

@ -79,6 +79,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}).Inc() }).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") 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 { if !flags.CanSearch {
pi.log.Debug("User can't search, showing info about user") pi.log.Debug("User can't search, showing info about user")
return pi.SearchMe(req, flags) return pi.SearchMe(req, flags)
@ -111,6 +116,12 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
"client": utils.GetIP(req.conn.RemoteAddr()), "client": utils.GetIP(req.conn.RemoteAddr()),
}).Inc() }).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter) 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: case GroupObjectClass:
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(2)
@ -160,7 +171,15 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
}() }()
wg.Wait() wg.Wait()
entries = append(gEntries, uEntries...) 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") uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
searchReq, skip := parseFilterForUser(c.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter, false) searchReq, skip := parseFilterForUser(c.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter, false)
if skip { if skip {
@ -181,99 +200,45 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
} }
func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { 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) 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} return &ldap.Entry{DN: dn, Attributes: attrs}
} }
func (pi *ProviderInstance) GroupEntry(g LDAPGroup) *ldap.Entry { func (pi *ProviderInstance) GroupEntry(g LDAPGroup) *ldap.Entry {
attrs := []*ldap.EntryAttribute{ attrs := AKAttrsToLDAP(g.akAttributes)
{
Name: "cn",
Values: []string{g.cn},
},
{
Name: "uid",
Values: []string{g.uid},
},
{
Name: "sAMAccountName",
Values: []string{g.cn},
},
{
Name: "gidNumber",
Values: []string{g.gidNumber},
},
}
objectClass := []string{GroupObjectClass, "groupOfUniqueNames", "goauthentik.io/ldap/group"}
if g.isVirtualGroup { if g.isVirtualGroup {
attrs = append(attrs, &ldap.EntryAttribute{ objectClass = append(objectClass, "goauthentik.io/ldap/virtual-group")
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)...)
} }
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} 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, SearchRequest: searchReq,
BindDN: bindDN, BindDN: bindDN,
conn: conn, 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, id: rid,
ctx: span.Context(), ctx: span.Context(),
} }
@ -60,7 +60,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
if err == nil { if err == nil {
return return
} }
log.WithError(err.(error)).Error("recover in serach request") log.WithError(err.(error)).Error("recover in search request")
sentry.CaptureException(err.(error)) sentry.CaptureException(err.(error))
}() }()
@ -74,7 +74,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
} }
for _, provider := range ls.providers { for _, provider := range ls.providers {
providerBase, _ := goldap.ParseDN(provider.BaseDN) providerBase, _ := goldap.ParseDN(provider.BaseDN)
if providerBase.AncestorOf(bd) { if providerBase.AncestorOf(bd) || providerBase.Equal(bd) {
return provider.Search(req) return provider.Search(req)
} }
} }

View File

@ -39,6 +39,9 @@ func ldapResolveTypeSingle(in interface{}) *string {
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute { func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
attrList := []*ldap.EntryAttribute{} attrList := []*ldap.EntryAttribute{}
if attrs == nil {
return attrList
}
a := attrs.(*map[string]interface{}) a := attrs.(*map[string]interface{})
for attrKey, attrValue := range *a { for attrKey, attrValue := range *a {
entry := &ldap.EntryAttribute{Name: attrKey} entry := &ldap.EntryAttribute{Name: attrKey}
@ -140,3 +143,26 @@ func (pi *ProviderInstance) GetRIDForGroup(uid string) int32 {
return int32(gid) 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 { func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoint {
authUrl := p.OidcConfiguration.AuthorizationEndpoint authUrl := p.OidcConfiguration.AuthorizationEndpoint
endUrl := p.OidcConfiguration.EndSessionEndpoint 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") host := os.Getenv("AUTHENTIK_HOST")
authUrl = strings.ReplaceAll(authUrl, host, browserHost) authUrl = strings.ReplaceAll(authUrl, host, browserHost)
endUrl = strings.ReplaceAll(endUrl, host, browserHost) endUrl = strings.ReplaceAll(endUrl, host, browserHost)

View File

@ -18,12 +18,22 @@ func GetStore(p api.ProxyOutpostConfig) sessions.Store {
if err != nil { if err != nil {
panic(err) 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 rs.Options.Domain = *p.CookieDomain
log.Info("using redis session backend") log.Info("using redis session backend")
store = rs store = rs
} else { } else {
cs := sessions.NewCookieStore([]byte(*p.CookieSecret)) cs := sessions.NewCookieStore([]byte(*p.CookieSecret))
cs.Options.Domain = *p.CookieDomain 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") log.Info("using cookie session backend")
store = cs store = cs
} }

View File

@ -45,6 +45,15 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
host := web.GetHost(r) host := web.GetHost(r)
a, ok := ps.apps[host] a, ok := ps.apps[host]
if !ok { 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") ps.log.WithField("host", host).Warning("no app for hostname")
rw.WriteHeader(400) rw.WriteHeader(400)
return return

View File

@ -36,13 +36,15 @@ func (ws *WebServer) configureStatic() {
} }
statRouter.PathPrefix("/static/dist/").Handler(distHandler) statRouter.PathPrefix("/static/dist/").Handler(distHandler)
statRouter.PathPrefix("/static/authentik/").Handler(authentikHandler) statRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
// Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on // Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on
statRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { statRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs).ServeHTTP(rw, r) disableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
}) })
statRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs)) statRouter.PathPrefix("/if/admin/assets").Handler(disableIndex(http.StripPrefix("/if/admin", distFs)))
statRouter.PathPrefix("/if/user/assets").Handler(disableIndex(http.StripPrefix("/if/user", distFs)))
statRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fs)) statRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fs))

View File

@ -17,4 +17,6 @@ COPY --from=builder /go/ldap /
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ] HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ]
EXPOSE 3389 6636 9300
ENTRYPOINT ["/ldap"] ENTRYPOINT ["/ldap"]

View File

@ -29,4 +29,6 @@ COPY --from=builder /go/proxy /
HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ] HEALTHCHECK CMD [ "wget", "--spider", "http://localhost:9300/akprox/ping" ]
EXPOSE 9000 9300 9443
ENTRYPOINT ["/proxy"] ENTRYPOINT ["/proxy"]

View File

@ -15,13 +15,7 @@ force_to_top = "*"
[tool.coverage.run] [tool.coverage.run]
source = ["authentik"] source = ["authentik"]
relative_files = true relative_files = true
omit = [ omit = ["*/asgi.py", "manage.py", "*/migrations/*", "*/apps.py", "website/"]
"*/asgi.py",
"manage.py",
"*/migrations/*",
"*/apps.py",
"website/",
]
[tool.coverage.report] [tool.coverage.report]
sort = "Cover" sort = "Cover"
@ -45,7 +39,7 @@ exclude_lines = [
show_missing = true show_missing = true
[tool.pylint.master] [tool.pylint.master]
disable =[ disable = [
"arguments-differ", "arguments-differ",
"no-self-use", "no-self-use",
"fixme", "fixme",
@ -60,20 +54,21 @@ disable =[
"protected-access", "protected-access",
"raise-missing-from", "raise-missing-from",
# To preverse django's translation function we need to use %-formatting # To preverse django's translation function we need to use %-formatting
"consider-using-f-string",] "consider-using-f-string",
]
load-plugins=["pylint_django","pylint.extensions.bad_builtin"] load-plugins = ["pylint_django", "pylint.extensions.bad_builtin"]
django-settings-module="authentik.root.settings" django-settings-module = "authentik.root.settings"
extension-pkg-whitelist=["lxml","xmlsec"] extension-pkg-whitelist = ["lxml", "xmlsec"]
# Allow constants to be shorter than normal (and lowercase, for settings.py) # Allow constants to be shorter than normal (and lowercase, for settings.py)
const-rgx="[a-zA-Z0-9_]{1,40}$" const-rgx = "[a-zA-Z0-9_]{1,40}$"
ignored-modules=["django-otp","binascii", "socket", "zlib"] ignored-modules = ["django-otp", "binascii", "socket", "zlib"]
generated-members=["xmlsec.constants.*","xmlsec.tree.*","xmlsec.template.*"] generated-members = ["xmlsec.constants.*", "xmlsec.tree.*", "xmlsec.template.*"]
ignore="migrations" ignore = "migrations"
max-attributes=12 max-attributes = 12
max-branches=20 max-branches = 20
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "authentik.root.settings" DJANGO_SETTINGS_MODULE = "authentik.root.settings"

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2021.9.1-rc3 version: 2021.9.3
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@beryju.org email: hello@beryju.org
@ -4702,8 +4702,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/ChallengeTypes' $ref: '#/components/schemas/ChallengeTypes'
description: '' description: ''
'404':
description: No Token found
'400': '400':
$ref: '#/components/schemas/ValidationError' $ref: '#/components/schemas/ValidationError'
'403': '403':
@ -11397,7 +11395,7 @@ paths:
/root/config/: /root/config/:
get: get:
operationId: root_config_retrieve operationId: root_config_retrieve
description: Retrive public configuration options description: Retrieve public configuration options
tags: tags:
- root - root
security: security:
@ -21582,6 +21580,7 @@ components:
readOnly: true readOnly: true
webhook_url: webhook_url:
type: string type: string
format: uri
webhook_mapping: webhook_mapping:
type: string type: string
format: uuid format: uuid
@ -21611,6 +21610,7 @@ components:
$ref: '#/components/schemas/NotificationTransportModeEnum' $ref: '#/components/schemas/NotificationTransportModeEnum'
webhook_url: webhook_url:
type: string type: string
format: uri
webhook_mapping: webhook_mapping:
type: string type: string
format: uuid format: uuid
@ -21908,7 +21908,7 @@ components:
access_token_url: access_token_url:
type: string type: string
nullable: true nullable: true
description: URL used by authentik to retrive tokens. description: URL used by authentik to retrieve tokens.
maxLength: 255 maxLength: 255
profile_url: profile_url:
type: string type: string
@ -21983,7 +21983,7 @@ components:
access_token_url: access_token_url:
type: string type: string
nullable: true nullable: true
description: URL used by authentik to retrive tokens. description: URL used by authentik to retrieve tokens.
maxLength: 255 maxLength: 255
profile_url: profile_url:
type: string type: string
@ -25810,6 +25810,7 @@ components:
$ref: '#/components/schemas/NotificationTransportModeEnum' $ref: '#/components/schemas/NotificationTransportModeEnum'
webhook_url: webhook_url:
type: string type: string
format: uri
webhook_mapping: webhook_mapping:
type: string type: string
format: uuid format: uuid
@ -25936,7 +25937,7 @@ components:
access_token_url: access_token_url:
type: string type: string
nullable: true nullable: true
description: URL used by authentik to retrive tokens. description: URL used by authentik to retrieve tokens.
maxLength: 255 maxLength: 255
profile_url: profile_url:
type: string type: string
@ -26114,7 +26115,7 @@ components:
description: Allow friends to authenticate, even if you don't share a server. description: Allow friends to authenticate, even if you don't share a server.
plex_token: plex_token:
type: string type: string
description: Plex token used to check firends description: Plex token used to check friends
PatchedPolicyBindingRequest: PatchedPolicyBindingRequest:
type: object type: object
description: PolicyBinding Serializer description: PolicyBinding Serializer
@ -26747,7 +26748,7 @@ components:
description: Allow friends to authenticate, even if you don't share a server. description: Allow friends to authenticate, even if you don't share a server.
plex_token: plex_token:
type: string type: string
description: Plex token used to check firends description: Plex token used to check friends
required: required:
- component - component
- name - name
@ -26842,7 +26843,7 @@ components:
description: Allow friends to authenticate, even if you don't share a server. description: Allow friends to authenticate, even if you don't share a server.
plex_token: plex_token:
type: string type: string
description: Plex token used to check firends description: Plex token used to check friends
required: required:
- name - name
- plex_token - plex_token
@ -27377,11 +27378,17 @@ components:
Exclusive with internal_host. Exclusive with internal_host.
cookie_domain: cookie_domain:
type: string type: string
token_validity:
type: number
format: float
nullable: true
readOnly: true
required: required:
- external_host - external_host
- name - name
- oidc_configuration - oidc_configuration
- pk - pk
- token_validity
ProxyProvider: ProxyProvider:
type: object type: object
description: ProxyProvider Serializer description: ProxyProvider Serializer

View File

@ -217,6 +217,7 @@ class TestProviderLDAP(SeleniumTestCase):
"objectClass": [ "objectClass": [
"user", "user",
"organizationalPerson", "organizationalPerson",
"inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
], ],
"uidNumber": [str(2000 + outpost_user.pk)], "uidNumber": [str(2000 + outpost_user.pk)],
@ -243,6 +244,7 @@ class TestProviderLDAP(SeleniumTestCase):
"objectClass": [ "objectClass": [
"user", "user",
"organizationalPerson", "organizationalPerson",
"inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
], ],
"uidNumber": [str(2000 + embedded_account.pk)], "uidNumber": [str(2000 + embedded_account.pk)],
@ -269,6 +271,7 @@ class TestProviderLDAP(SeleniumTestCase):
"objectClass": [ "objectClass": [
"user", "user",
"organizationalPerson", "organizationalPerson",
"inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
], ],
"uidNumber": [str(2000 + USER().pk)], "uidNumber": [str(2000 + USER().pk)],

View File

@ -6,3 +6,5 @@ dist
coverage coverage
# don't lint generated code # don't lint generated code
api/ api/
# Import order matters
poly.ts

View File

@ -14,5 +14,7 @@
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "all", "trailingComma": "all",
"useTabs": false, "useTabs": false,
"vueIndentScriptAndStyle": false "vueIndentScriptAndStyle": false,
"importOrder": ["^@lingui/(.*)$", "^lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true
} }

680
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,30 +47,31 @@
"@babel/preset-env": "^7.15.6", "@babel/preset-env": "^7.15.6",
"@babel/preset-typescript": "^7.15.0", "@babel/preset-typescript": "^7.15.0",
"@fortawesome/fontawesome-free": "^5.15.4", "@fortawesome/fontawesome-free": "^5.15.4",
"@goauthentik/api": "^2021.9.1-rc2-1631875394", "@goauthentik/api": "^2021.9.2-1632578262",
"@lingui/cli": "^3.11.1", "@lingui/cli": "^3.11.1",
"@lingui/core": "^3.11.1", "@lingui/core": "^3.11.1",
"@lingui/macro": "^3.11.1", "@lingui/macro": "^3.11.1",
"@patternfly/patternfly": "^4.132.2", "@patternfly/patternfly": "^4.135.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
"@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5", "@rollup/plugin-typescript": "^8.2.5",
"@sentry/browser": "^6.12.0", "@sentry/browser": "^6.13.2",
"@sentry/tracing": "^6.12.0", "@sentry/tracing": "^6.13.2",
"@squoosh/cli": "^0.7.2", "@squoosh/cli": "^0.7.2",
"@trivago/prettier-plugin-sort-imports": "^2.0.4",
"@types/chart.js": "^2.9.34", "@types/chart.js": "^2.9.34",
"@types/codemirror": "5.60.2", "@types/codemirror": "5.60.3",
"@types/grecaptcha": "^3.0.3", "@types/grecaptcha": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/eslint-plugin": "^4.31.2",
"@typescript-eslint/parser": "^4.31.1", "@typescript-eslint/parser": "^4.31.2",
"@webcomponents/webcomponentsjs": "^2.6.0", "@webcomponents/webcomponentsjs": "^2.6.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.5.1", "chart.js": "^3.5.1",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.62.3", "codemirror": "^5.63.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
@ -78,12 +79,11 @@
"eslint-plugin-lit": "^1.5.1", "eslint-plugin-lit": "^1.5.1",
"flowchart.js": "^1.15.0", "flowchart.js": "^1.15.0",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"lit-element": "^2.5.1", "lit": "^2.0.0",
"lit-html": "^1.4.1",
"moment": "^2.29.1", "moment": "^2.29.1",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"rapidoc": "^9.1.0", "rapidoc": "^9.1.3",
"rollup": "^2.56.2", "rollup": "^2.57.0",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",

View File

@ -2,3 +2,4 @@
window["polymerSkipLoadingFontRoboto"] = true; window["polymerSkipLoadingFontRoboto"] = true;
import "construct-style-sheets-polyfill"; import "construct-style-sheets-polyfill";
import "@webcomponents/webcomponentsjs"; import "@webcomponents/webcomponentsjs";
import "lit/polyfill-support";

View File

@ -1,11 +1,11 @@
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import { terser } from "rollup-plugin-terser";
import sourcemaps from "rollup-plugin-sourcemaps";
import cssimport from "rollup-plugin-cssimport";
import copy from "rollup-plugin-copy";
import babel from "@rollup/plugin-babel"; import babel from "@rollup/plugin-babel";
import replace from "@rollup/plugin-replace"; import replace from "@rollup/plugin-replace";
import commonjs from "rollup-plugin-commonjs";
import copy from "rollup-plugin-copy";
import cssimport from "rollup-plugin-cssimport";
import resolve from "rollup-plugin-node-resolve";
import sourcemaps from "rollup-plugin-sourcemaps";
import { terser } from "rollup-plugin-terser";
const extensions = [".js", ".jsx", ".ts", ".tsx"]; const extensions = [".js", ".jsx", ".ts", ".tsx"];

View File

@ -1,4 +1,5 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { EVENT_WS_MESSAGE } from "../constants"; import { EVENT_WS_MESSAGE } from "../constants";
import { MessageLevel } from "../elements/messages/Message"; import { MessageLevel } from "../elements/messages/Message";
import { showMessage } from "../elements/messages/MessageContainer"; import { showMessage } from "../elements/messages/MessageContainer";

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger"; export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2021.9.1-rc3"; export const VERSION = "2021.9.3";
export const PAGE_SIZE = 20; export const PAGE_SIZE = 20;
export const TITLE_DEFAULT = "authentik"; export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";"; export const ROUTE_SEPARATOR = ";";

View File

@ -1,29 +1,23 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import CodeMirror from "codemirror"; import CodeMirror from "codemirror";
import "codemirror/addon/dialog/dialog";
import "codemirror/addon/display/autorefresh"; import "codemirror/addon/display/autorefresh";
import "codemirror/addon/hint/show-hint";
import "codemirror/addon/search/search"; import "codemirror/addon/search/search";
import "codemirror/addon/search/searchcursor"; import "codemirror/addon/search/searchcursor";
import "codemirror/addon/dialog/dialog";
import "codemirror/addon/hint/show-hint";
import "codemirror/mode/xml/xml.js";
import "codemirror/mode/yaml/yaml.js";
import "codemirror/mode/javascript/javascript.js"; import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/python/python.js"; import "codemirror/mode/python/python.js";
import CodeMirrorStyle from "codemirror/lib/codemirror.css"; import "codemirror/mode/xml/xml.js";
import CodeMirrorTheme from "codemirror/theme/monokai.css"; import "codemirror/mode/yaml/yaml.js";
import YAML from "yaml";
import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import CodeMirrorDialogStyle from "codemirror/addon/dialog/dialog.css"; import CodeMirrorDialogStyle from "codemirror/addon/dialog/dialog.css";
import CodeMirrorShowHintStyle from "codemirror/addon/hint/show-hint.css"; import CodeMirrorShowHintStyle from "codemirror/addon/hint/show-hint.css";
import { ifDefined } from "lit-html/directives/if-defined"; import CodeMirrorStyle from "codemirror/lib/codemirror.css";
import YAML from "yaml"; import CodeMirrorTheme from "codemirror/theme/monokai.css";
@customElement("ak-codemirror") @customElement("ak-codemirror")
export class CodeMirrorTextarea extends LitElement { export class CodeMirrorTextarea extends LitElement {

View File

@ -1,6 +1,8 @@
import { css, CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { customElement } from "lit/decorators";
import AKGlobal from "../authentik.css"; import AKGlobal from "../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-divider") @customElement("ak-divider")
export class Divider extends LitElement { export class Divider extends LitElement {

View File

@ -1,8 +1,10 @@
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, html, LitElement, TemplateResult } from "lit";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; import { customElement, property } from "lit/decorators";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import AKGlobal from "../authentik.css"; import AKGlobal from "../authentik.css";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { PFSize } from "./Spinner"; import { PFSize } from "./Spinner";

View File

@ -1,5 +1,8 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { CSSResult, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import PFExpandableSection from "../../node_modules/@patternfly/patternfly/components/ExpandableSection/expandable-section.css"; import PFExpandableSection from "../../node_modules/@patternfly/patternfly/components/ExpandableSection/expandable-section.css";
@customElement("ak-expand") @customElement("ak-expand")

View File

@ -1,7 +1,9 @@
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, html, LitElement, TemplateResult } from "lit";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { customElement, property } from "lit/decorators";
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
import AKGlobal from "../authentik.css"; import AKGlobal from "../authentik.css";
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export enum PFColor { export enum PFColor {
Green = "pf-m-green", Green = "pf-m-green",

View File

@ -1,13 +1,8 @@
import { import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { PFSize } from "./Spinner"; import { PFSize } from "./Spinner";
@customElement("ak-loading-overlay") @customElement("ak-loading-overlay")

View File

@ -1,25 +1,22 @@
import { import { css, CSSResult, html, LitElement, TemplateResult } from "lit";
css, import { customElement, property } from "lit/decorators";
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../authentik.css"; import AKGlobal from "../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { EventsApi } from "@goauthentik/api";
import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { import {
EVENT_API_DRAWER_TOGGLE, EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE,
EVENT_REFRESH,
EVENT_SIDEBAR_TOGGLE, EVENT_SIDEBAR_TOGGLE,
TITLE_DEFAULT, TITLE_DEFAULT,
} from "../constants"; } from "../constants";
import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { EventsApi } from "@goauthentik/api";
@customElement("ak-page-header") @customElement("ak-page-header")
export class PageHeader extends LitElement { export class PageHeader extends LitElement {
@ -90,14 +87,11 @@ export class PageHeader extends LitElement {
]; ];
} }
renderIcon(): TemplateResult { constructor() {
if (this.icon) { super();
if (this.iconImage) { window.addEventListener(EVENT_REFRESH, () => {
return html`<img class="pf-icon" src="${this.icon}" />&nbsp;`; this.firstUpdated();
} });
return html`<i class=${this.icon}></i>&nbsp;`;
}
return html``;
} }
firstUpdated(): void { firstUpdated(): void {
@ -112,6 +106,16 @@ export class PageHeader extends LitElement {
}); });
} }
renderIcon(): TemplateResult {
if (this.icon) {
if (this.iconImage) {
return html`<img class="pf-icon" src="${this.icon}" />&nbsp;`;
}
return html`<i class=${this.icon}></i>&nbsp;`;
}
return html``;
}
render(): TemplateResult { render(): TemplateResult {
return html`<button return html`<button
class="sidebar-trigger pf-c-button pf-m-plain" class="sidebar-trigger pf-c-button pf-m-plain"

View File

@ -1,5 +1,8 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { CSSResult, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css"; import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
export enum PFSize { export enum PFSize {

View File

@ -1,18 +1,14 @@
import { import { t } from "@lingui/macro";
LitElement,
html, import { LitElement, html, CSSResult, TemplateResult, css } from "lit";
customElement, import { customElement, property } from "lit/decorators";
property, import { ifDefined } from "lit/directives/if-defined";
CSSResult,
TemplateResult, import AKGlobal from "../authentik.css";
css,
} from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css"; import PFTabs from "@patternfly/patternfly/components/Tabs/tabs.css";
import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../authentik.css";
import { CURRENT_CLASS, ROUTE_SEPARATOR } from "../constants"; import { CURRENT_CLASS, ROUTE_SEPARATOR } from "../constants";
import { t } from "@lingui/macro";
@customElement("ak-tabs") @customElement("ak-tabs")
export class Tabs extends LitElement { export class Tabs extends LitElement {

View File

@ -1,17 +1,17 @@
import { customElement, property } from "lit-element"; import { customElement, property } from "lit/decorators";
import { SpinnerButton } from "./SpinnerButton";
import { showMessage } from "../messages/MessageContainer";
import { MessageLevel } from "../messages/Message"; import { MessageLevel } from "../messages/Message";
import { showMessage } from "../messages/MessageContainer";
import { SpinnerButton } from "./SpinnerButton";
@customElement("ak-action-button") @customElement("ak-action-button")
export class ActionButton extends SpinnerButton { export class ActionButton extends SpinnerButton {
@property({ attribute: false }) @property({ attribute: false })
// eslint-disable-next-line @typescript-eslint/no-explicit-any apiRequest: () => Promise<unknown> = () => {
apiRequest: () => Promise<any> = () => {
throw new Error(); throw new Error();
}; };
callAction = (): Promise<void> => { callAction = (): Promise<unknown> => {
this.setLoading(); this.setLoading();
return this.apiRequest().catch((e: Error | Response) => { return this.apiRequest().catch((e: Error | Response) => {
if (e instanceof Error) { if (e instanceof Error) {

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