Compare commits

..

148 Commits

Author SHA1 Message Date
fe5d22ce6c release: 2021.8.5 2021-09-10 22:10:35 +02:00
0e30b6ee55 lifecycle: fix worker startup error when docker socket's group is not called docker
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-10 22:05:00 +02:00
6cbba45291 web: ignore network error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-10 21:51:11 +02:00
ba023a3bba outpost: update global outpost config on refresh
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-10 21:51:02 +02:00
6c805bcf32 sources/oauth: don't cancel flow when redirecting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-10 21:50:45 +02:00
bc7d5042df outpost/proxy: use common template for proxy error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 16:44:15 +02:00
de3e1c3dbc sources/oauth: fix FlowExecutor view call
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 11:39:03 +02:00
3c6aac5435 sources/oauth: prevent potentially confidential data from being logged
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 11:05:18 +02:00
eeb755ab7d root: show location header in logs when redirecting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 11:04:00 +02:00
70d0dd51a5 sources/oauth: cancel currently active flows before redirecting out
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 11:03:45 +02:00
073dd8b560 web/admin: fix notification clear all not triggering render
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 10:23:55 +02:00
b5d2924d46 website/docs: update 2021.8.5
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 10:10:43 +02:00
597e279f34 ci: fix old node version in release ci
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-07 10:07:36 +02:00
fc28def83d build(deps): bump @typescript-eslint/eslint-plugin in /web (#1358) 2021-09-07 08:42:57 +02:00
f6efdfded4 build(deps): bump @typescript-eslint/parser in /web (#1357) 2021-09-07 08:31:13 +02:00
91312496e0 ci: simplify testspace setup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-06 16:52:21 +02:00
b557b4337d build(deps): bump @babel/core from 7.15.4 to 7.15.5 in /web (#1351) 2021-09-06 08:36:40 +02:00
bfde186aa0 build(deps): bump actions/cache from 1 to 2.1.6 (#1352) 2021-09-06 08:36:32 +02:00
2bd75dd1a9 build(deps): bump xmlsec from 1.3.11 to 1.3.12 (#1353) 2021-09-06 08:36:16 +02:00
27ab31a9b0 build(deps): bump boto3 from 1.18.35 to 1.18.36 (#1354) 2021-09-06 08:35:56 +02:00
44a8b737d9 build(deps): bump drf-spectacular from 0.18.1 to 0.18.2 (#1355) 2021-09-06 08:35:45 +02:00
b939ee7a09 website/docs: use kubectl exec with deployment, add note for backup version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1349
2021-09-05 20:25:42 +02:00
0bae550520 root: include authentik version in backup naming
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-05 20:25:02 +02:00
b5cc2f2bda website/docs: add missing ENV, changed k8s beta instructions (#1350)
* fixed IsActive and IsSuperuser return string

IsActive and IsSuperuser attributes were interchanged.

* updated docs

Co-authored-by: Tobias Mandjik <tobias.mandjik@linogics.io>
2021-09-05 19:58:42 +02:00
9ad4cf1db9 outposts/ldap: improve logging of client IPs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-05 19:47:30 +02:00
9dbafaaea2 web: Update Web API Client version (#1348)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 22:49:16 +02:00
2db8b07578 events: add mark_all_seen
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 22:08:12 +02:00
7c1a7bfd9d ci: use native kind action to test integration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 16:06:44 +02:00
b7ef076798 outposts: add expected outpost replica count to metrics
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 15:56:57 +02:00
37c29a073e policies/password: fix symbols not being checked correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 15:21:48 +02:00
0c288ea64b ci: cache webui for e2e tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 15:21:24 +02:00
2476475174 ci: attempt to cache pipenv (#1347)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:55:54 +02:00
71913c8164 website/docs: fix typos in vikunja docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:38:35 +02:00
6ec8432217 policies/password: don't use regex for symbol detection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:36:01 +02:00
7a12c0e4d1 web/admin: fix user selection in token form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:33:40 +02:00
23a7eba16b website/docs: add 8.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:09:11 +02:00
3ba84a8e8b stages/identification: fix empty user_fields query returning first user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 14:07:14 +02:00
75476217a0 internal: fix web requests not having a logger set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 13:52:47 +02:00
7771c0b905 internal: fix font loading errors on safari
closes #1057

for some reason safari appends the relative font path to the document URL not to the stylesheet URL. Since I don't want to build a fully custom patternfly base css file, this mounts the static files where safari expects them

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 13:50:29 +02:00
3378e82ec7 root: fix is_secure with safari on debug environments
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 13:45:50 +02:00
126e43dea4 internal: disable directory listing on static files
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 13:40:29 +02:00
f725009530 web/flows: fix display error when using IdentificationStage without input fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-04 13:06:37 +02:00
70d1e3a0cb outpost: fix spans being sent without parent context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 18:17:08 +02:00
e751ce1220 root: update badges
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 18:11:13 +02:00
e09a27cf87 events: remove authentik_events gauge
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 18:04:26 +02:00
06fbf44724 root: update security.md
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 17:26:10 +02:00
200e409d91 core: minor query optimization
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 14:02:57 +02:00
5e5854e256 ci: fix invalid workflow
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 10:58:42 +02:00
3df8bcfc9c web: Update Web API Client version (#1345)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-03 10:53:59 +02:00
e76c14f9e0 ci: run on pr and improve checking for push
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 10:53:39 +02:00
6b6748b1c7 web/admin: show applications instead of providers in outpost form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 10:43:21 +02:00
d92d8e6dbb api: add additional filters for ldap and proxy providers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 10:43:09 +02:00
c2b9dc5c75 api: cache schema, fix server urls
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-03 10:23:14 +02:00
5c1d27de2b build(deps): bump docker from 5.0.1 to 5.0.2 (#1343) 2021-09-03 08:46:33 +02:00
6ab9e7cd68 build(deps): bump @babel/core from 7.15.0 to 7.15.4 in /web (#1339) 2021-09-03 08:46:23 +02:00
3ef56e9ec1 build(deps): bump @docusaurus/plugin-client-redirects in /website (#1338) 2021-09-03 08:46:05 +02:00
6d8d157772 build(deps): bump @babel/plugin-proposal-decorators in /web (#1340) 2021-09-03 08:44:36 +02:00
cadd466eec build(deps): bump @docusaurus/preset-classic in /website (#1341) 2021-09-03 08:44:27 +02:00
3fea0c1e49 build(deps): bump @babel/preset-env from 7.15.0 to 7.15.4 in /web (#1342) 2021-09-03 08:44:16 +02:00
4c58201adc build(deps): bump boto3 from 1.18.34 to 1.18.35 (#1344) 2021-09-03 08:44:02 +02:00
4fb4e72624 web: Update Web API Client version (#1337)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-02 21:34:04 +02:00
276d8fe5cf release: 2021.8.4 2021-09-02 20:21:21 +02:00
92ce5f0931 web: improve error display when only {'detail'} is returned
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 19:55:37 +02:00
7fea20375f *: fix tests not using APITestCase
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 19:14:21 +02:00
d4d4034d2c web: Update Web API Client version (#1336)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-02 17:42:55 +02:00
f0db408699 api: add v3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 17:40:02 +02:00
5e200655d9 web: Update Web API Client version (#1335)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-02 17:13:16 +02:00
d5d1f2a645 web: show version in logs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 17:10:43 +02:00
cc5cc43baa api: fix sentry endpoint not working due to mime-media
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 16:56:53 +02:00
e512f085db root: allow enabling s3 backup ssl verification
closes #1332

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-02 09:41:55 +02:00
f323c01bd8 build(deps): bump django from 3.2.6 to 3.2.7 (#1333) 2021-09-02 09:12:24 +02:00
f56cacb406 build(deps): bump boto3 from 1.18.33 to 1.18.34 (#1334) 2021-09-02 09:12:03 +02:00
eaecd31e9f ci: always run codecov and testspace
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 22:59:51 +02:00
36989d82e1 ci: merge on testspace
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 22:35:17 +02:00
50777d9022 ci: re-add testspace (#1331)
* ci: re-add testspace

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

* ci: fix double k3d

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 22:33:10 +02:00
a15571bd3e outposts/proxy: detect empty authentik_host
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 22:09:07 +02:00
26fd66d831 stages/authenticator_validate: fix variable shadowing, optimization
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 19:54:54 +02:00
0be873025a ci: fix bumpversion path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 19:38:04 +02:00
28ada49910 website/docs: final 2021.8.4 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 19:37:00 +02:00
4fc8e61f8c stages/authenticator_validate: show single button for multiple webauthn authenticators
tested with browser + yubikey 5

closes #1096

The order of allowCredentials doesn't seem to matter, chrome seems to always choose the internal authenticator first.

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 19:28:52 +02:00
7d26ea1a9c web/admin: fix list of webauthn devices not updating after rename
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 19:05:18 +02:00
3a58dc62e1 ci: fix missing branch
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 18:34:57 +02:00
71fe7bc827 ci: fix sha being used instead of timestamp
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 17:10:42 +02:00
933336c38b ci: fix images not being pushed with correct tags
* ci: debug

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

* ci: fix branch and sha

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 16:19:29 +02:00
371feb9a31 ci: fix images not being pushed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 15:07:13 +02:00
95a2fd3c9e web: Update Web API Client version (#1327)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-01 14:48:48 +02:00
17cb76c334 stages/invitation: fix invitation not inheriting ExpiringModel
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 14:25:19 +02:00
88f0dfc8cc web/admin: fallback for invitation list on first load
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 13:33:05 +02:00
f82aada23b web/admin: fix flow executor not opening in new tab
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-01 13:19:09 +02:00
ecaee92634 build(deps): bump @sentry/tracing from 6.11.0 to 6.12.0 in /web (#1322)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.11.0...6.12.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  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-01 09:26:53 +02:00
89252ec47b build(deps): bump @sentry/tracing from 6.11.0 to 6.12.0 in /website (#1320)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.11.0...6.12.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  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-01 09:26:33 +02:00
f0f25ab291 build(deps): bump @sentry/react from 6.11.0 to 6.12.0 in /website (#1321) 2021-09-01 08:40:07 +02:00
e4d0fec15a build(deps): bump @sentry/browser from 6.11.0 to 6.12.0 in /web (#1323) 2021-09-01 08:39:56 +02:00
6b10baf086 build(deps): bump docker from 5.0.0 to 5.0.1 (#1324) 2021-09-01 08:39:21 +02:00
f148b5d341 build(deps): bump boto3 from 1.18.32 to 1.18.33 (#1326) 2021-09-01 08:39:12 +02:00
1471ff8940 build(deps): bump drf-spectacular from 0.18.0 to 0.18.1 (#1325) 2021-09-01 08:39:01 +02:00
d9a6ec2ac0 webiste/docs: update extensionvs/v1beta ingress
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-31 21:11:01 +02:00
5745ffa0a8 ci: don't login to docker on forks
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-31 09:31:10 +02:00
b26202db35 build(deps): bump @typescript-eslint/parser in /web (#1316) 2021-08-31 08:42:14 +02:00
6318577a51 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1317) 2021-08-31 08:16:59 +02:00
6a2cd45847 build(deps-dev): bump pytest from 6.2.4 to 6.2.5 (#1318) 2021-08-31 08:16:44 +02:00
ef5cea2c01 build(deps): bump boto3 from 1.18.31 to 1.18.32 (#1319) 2021-08-31 08:16:32 +02:00
69f4d54bae ci: migrate ci to gh actions (#1315) 2021-08-30 20:21:15 +02:00
b1eec5a7d2 outposts/proxy: add more logging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-30 17:18:52 +02:00
1b8271d767 flows: disable compatibility_mode by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-30 17:18:43 +02:00
3e9f5ec5ef providers/proxy: improve error handling for non-tls ingresses
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-30 14:43:57 +02:00
63f57b6a77 events: improve logging for task exceptions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-30 14:43:44 +02:00
a016f99450 core: fix user_obj being empty on token API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-30 12:51:17 +02:00
adc18b2991 build(deps): bump boto3 from 1.18.30 to 1.18.31 (#1314)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.30 to 1.18.31.
- [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.30...1.18.31)

---
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-08-30 09:12:58 +02:00
e37a326b95 website/docs: prepare 8.4 docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 22:12:49 +02:00
048467e97d outpost/ldap: delay user information removal upon closing of connection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 21:13:46 +02:00
cc2cd6919f outpost/embedded: only send requests for non-akprox paths when we're doing proxy mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 21:13:28 +02:00
0c6e781e5b providers/proxy: fix traefik middleware being generated with wrong ports for embedded outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 20:49:11 +02:00
7294d8fca5 website/docs: add note for cross-namespace reference in traefik
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 20:46:17 +02:00
16ec5680b4 web: Update Web API Client version (#1313)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-29 19:51:10 +02:00
87920fb1d7 website/docs: add docs for websocket connections
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 19:49:18 +02:00
523b96a6d2 api: add basic rate limiting for sentry endpoint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 19:33:18 +02:00
45731d8069 cmd: add option to disable embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 19:19:13 +02:00
e872371970 website/docs: add embedded outpost docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 14:43:13 +02:00
08e8cf850a web/flows: fix FlowExecutor not updating when challenge changes from outside
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 13:49:57 +02:00
b1ed2154ac policies/password: fix PasswordStage not being usable with prompt stages, rework validation logic
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-29 00:40:36 +02:00
7ef2aa3eb9 web: Update Web API Client version (#1312) 2021-08-28 19:08:38 +02:00
160139813d release: 2021.8.3 2021-08-28 16:58:44 +02:00
582ad92c76 outposts/k8s: improve error handling
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-28 14:58:26 +02:00
f61736e3d1 stages/identification: add error handling when password isn't set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-28 12:54:10 +02:00
eb02c96281 website/docs: make it clearer to use context[]
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-28 12:53:57 +02:00
8619552920 website/docs: prepare 2021.8.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 23:12:53 +02:00
6237352e25 web/flows: fix checkboxes not being rendered correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 23:09:53 +02:00
2d8b4f543b providers/proxy: fix url parsing for traefik labels on docker containers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 22:21:16 +02:00
8542dc10ab providers/proxy: fix docker container labels not being inherited correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 20:20:34 +02:00
c55b63337c web/flows: fix post-challenge updates not always being called by using setter
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 19:45:23 +02:00
12ddee3bb6 outpost: add additional labels to docker container
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 19:26:27 +02:00
dc41d0af27 outposts: add configurable docker_network for outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 19:26:11 +02:00
3323b50036 web/flows: also check for redirects as result of posting challenge
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 10:08:15 +02:00
8acb15a7fd outpost: fix flow executor not sending password for identification stage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 09:43:07 +02:00
f601e04b38 web/flows: assign location from redirect challenge in request handler not render
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-27 09:43:00 +02:00
f50529cb5b build(deps): bump @docusaurus/preset-classic in /website (#1307)
Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 2.0.0-beta.4 to 2.0.0-beta.5.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.5/packages/docusaurus-preset-classic)

---
updated-dependencies:
- dependency-name: "@docusaurus/preset-classic"
  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-08-27 09:20:09 +02:00
3f1b6f9ed4 build(deps): bump typescript from 4.3.5 to 4.4.2 in /web (#1306) 2021-08-27 08:36:43 +02:00
f1ab0f4314 build(deps): bump @patternfly/patternfly from 4.125.3 to 4.132.2 in /web (#1308) 2021-08-27 08:36:34 +02:00
4d1129f385 build(deps): bump boto3 from 1.18.29 to 1.18.30 (#1310) 2021-08-27 08:36:19 +02:00
03ac9c6e16 build(deps): bump @docusaurus/plugin-client-redirects in /website (#1309) 2021-08-27 08:36:11 +02:00
c0839924f1 build(deps): bump github.com/go-openapi/runtime from 0.19.30 to 0.19.31 (#1311) 2021-08-27 08:35:57 +02:00
91e3aa760a web: Update Web API Client version (#1305)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-26 19:06:13 +02:00
5c0681d57b website/docs: add 2021.8.2 docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-26 18:56:42 +02:00
c4f72c2bc1 release: 2021.8.2 2021-08-26 17:58:20 +02:00
e92f9836e3 root: allow django auth backend for upgrading users with cache
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-26 17:57:25 +02:00
3818dc834b web: Update Web API Client version (#1304)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-26 16:57:12 +02:00
cda011a049 website/docs: add 2021.8.1 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-26 16:06:27 +02:00
164 changed files with 3485 additions and 4626 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.8.1
current_version = 2021.8.5
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
@ -23,7 +23,7 @@ values =
[bumpversion:file:schema.yml]
[bumpversion:file:.github/workflows/release.yml]
[bumpversion:file:.github/workflows/release-publish.yml]
[bumpversion:file:authentik/__init__.py]

302
.github/workflows/ci-main.yml vendored Normal file
View File

@ -0,0 +1,302 @@
name: authentik-ci-main
on:
push:
branches:
- master
- next
- version-*
paths-ignore:
- website
pull_request:
branches:
- master
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
lint-pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run pylint
run: pipenv run pylint authentik tests lifecycle
lint-black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run black
run: pipenv run black --check authentik tests lifecycle
lint-isort:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run isort
run: pipenv run isort --check authentik tests lifecycle
lint-bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run bandit
run: pipenv run bandit -r authentik tests lifecycle
lint-pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: prepare
run: |
scripts/ci_prepare.sh
npm install -g pyright@1.1.136
- name: run bandit
run: pipenv run pyright e2e lifecycle
test-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations
run: pipenv run python -m lifecycle.migrate
test-migrations-from-stable:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: checkout stable
run: |
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
git checkout $(git describe --abbrev=0 --match 'version/*')
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations to stable
run: pipenv run python -m lifecycle.migrate
- name: checkout current code
run: |
set -x
git checkout $GITHUB_REF
pipenv sync --dev
- name: migrate to latest
run: pipenv run python -m lifecycle.migrate
test-unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- name: run unittest
run: |
pipenv run make test
pipenv run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [unittest]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
test-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.2.0
- name: run integration
run: |
pipenv run make test-integration
pipenv run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [integration]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
test-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/ci.docker-compose.yml up -d
- id: cache-web
uses: actions/cache@v2.1.6
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
run: |
cd web
npm i
npm run build
- name: run e2e
run: |
pipenv run make test-e2e
pipenv run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [e2e]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
build:
needs:
- lint-pylint
- lint-black
- lint-isort
- lint-bandit
- lint-pyright
- test-migrations
- test-migrations-from-stable
- test-unittest
- test-integration
- test-e2e
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: prepare variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
run: |
python ./scripts/gh_do_set_branch.py
- name: Login to Container Registry
uses: docker/login-action@v1
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: beryju.org
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Building Docker Image
uses: docker/build-push-action@v2
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}
beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}

75
.github/workflows/ci-outpost.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: authentik-ci-outpost
on:
push:
branches:
- master
- next
- version-*
pull_request:
branches:
- master
jobs:
lint-golint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16.3'
- name: Generate API
run: |
make gen-outpost
- name: Run linter
run: |
# Create folder structure for go embeds
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci-lint run -v --timeout 200s
build:
needs:
- lint-golint
strategy:
matrix:
type:
- proxy
- ldap
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: prepare variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
run: |
python ./scripts/gh_do_set_branch.py
- name: Login to Container Registry
uses: docker/login-action@v1
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: beryju.org
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Building Docker Image
uses: docker/build-push-action@v2
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }}
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}

89
.github/workflows/ci-web.yml vendored Normal file
View File

@ -0,0 +1,89 @@
name: authentik-ci-web
on:
push:
branches:
- master
- next
- version-*
pull_request:
branches:
- master
jobs:
lint-eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- name: Generate API
run: make gen-web
- name: Eslint
run: |
cd web
npm run lint
lint-prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- name: Generate API
run: make gen-web
- name: prettier
run: |
cd web
npm run prettier-check
lint-lit-analyse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- name: Generate API
run: make gen-web
- name: prettier
run: |
cd web
npm run lit-analyse
build:
needs:
- lint-eslint
- lint-prettier
- lint-lit-analyse
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- run: |
cd web
npm install
- name: Generate API
run: make gen-web
- name: build
run: |
cd web
npm run build

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.8.1,
beryju/authentik:2021.8.5,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.8.1,
ghcr.io/goauthentik/server:2021.8.5,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.5', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.8.1,
beryju/authentik-proxy:2021.8.5,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.8.1,
ghcr.io/goauthentik/proxy:2021.8.5,
ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.5', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.8.1,
beryju/authentik-ldap:2021.8.5,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.8.1,
ghcr.io/goauthentik/ldap:2021.8.5,
ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.5', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -157,9 +157,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.4.0
uses: actions/setup-node@v2
with:
node-version: 12.x
node-version: '16'
- name: Build web api client and web ui
run: |
export NODE_ENV=production
@ -175,7 +175,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.8.1
version: authentik@2021.8.5
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -12,7 +12,7 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2
with:
node-version: '16.x'
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- name: Generate API Client
run: make gen-web

View File

@ -98,5 +98,6 @@ COPY --from=builder /work/authentik /authentik-proxy
USER authentik
ENV TMPDIR /dev/shm/
ENV PYTHONUBUFFERED 1
ENV prometheus_multiproc_dir /dev/shm/
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/lifecycle"
ENTRYPOINT [ "/lifecycle/ak" ]

View File

@ -7,8 +7,6 @@ NPM_VERSION = $(shell python -m scripts.npm_version)
all: lint-fix lint test gen
test-integration:
k3d cluster create || exit 0
k3d kubeconfig write -o ~/.kube/config --overwrite
coverage run manage.py test -v 3 tests/integration
test-e2e:
@ -61,7 +59,7 @@ gen-outpost:
-i /local/schema.yml \
-g go \
-o /local/api \
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true,disallowAdditionalPropertiesIfNotPresent=false
rm -f api/go.mod api/go.sum
gen: gen-build gen-clean gen-web gen-outpost

178
Pipfile.lock generated
View File

@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:4dc7e346e92c01e8a997daa58a4c990151841d2d2962067325d963f665c7287a",
"sha256:79b7e6e0167def749352968ed6eb96954d9e2dd1dca8f297f122414753ce73a3"
"sha256:4df1085f5c24504a1b1a6584947f27b67c26eda123f29d3cecce9b2fd683e09b",
"sha256:a7fccb61d95230322dd812629455df14167307c569077fa89d297eae73605ffb"
],
"index": "pypi",
"version": "==1.18.29"
"version": "==1.18.36"
},
"botocore": {
"hashes": [
"sha256:1f16998b4f5a88e6844196feee7fa5eef6b36034d377f9845c7df12b8803b3be",
"sha256:fec924f63b40bd29b522fa109ecbc45f16eedcbeb22b68c6c79773c22a552b16"
"sha256:5b9a7d30e44b8a0a2bbbde62ae01bf6c349017e836985a0248552b00bbce7fae",
"sha256:e3e522fbe0bad1197aa7182451dc05f650310e77cf0a77749f6a5e82794c53de"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.29"
"version": "==1.21.36"
},
"cachetools": {
"hashes": [
@ -359,11 +359,11 @@
},
"django": {
"hashes": [
"sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13",
"sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022"
"sha256:95b318319d6997bac3595517101ad9cc83fe5672ac498ba48d1a410f47afecd2",
"sha256:e93c93565005b37ddebf2396b4dc4b6913c1838baa82efdfb79acedd5816c240"
],
"index": "pypi",
"version": "==3.2.6"
"version": "==3.2.7"
},
"django-dbbackup": {
"git": "https://github.com/django-dbbackup/django-dbbackup.git",
@ -443,19 +443,19 @@
},
"docker": {
"hashes": [
"sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5",
"sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"
"sha256:21ec4998e90dff7a7aaaa098ca8d839c7de412b89e6f6c30908372d58fecf663",
"sha256:9b17f0723d83c1f3418d2aa17bf90b24dbe97deda06208dd4262fa30a6ee87eb"
],
"index": "pypi",
"version": "==5.0.0"
"version": "==5.0.2"
},
"drf-spectacular": {
"hashes": [
"sha256:5b1c27de127c86564be5a967a6fa195cfe161b552d98364282ae9e6ed3d75a85",
"sha256:8588706c27f44adfbb3405bae9ef9cd6506f4b59d4cbd66c59780dce035602d9"
"sha256:47ef6ec8ff48ac8aede6ec12450a55fee381cf84de969ef1724dcde5a93de6b8",
"sha256:d746b936cb4cddec380ea95bf91de6a6721777dfc42e0eea53b83c61a625e94e"
],
"index": "pypi",
"version": "==0.18.0"
"version": "==0.18.2"
},
"duo-client": {
"hashes": [
@ -490,11 +490,11 @@
},
"google-auth": {
"hashes": [
"sha256:c012c8be7c442c8309ca8fa0876fef33f5fd977c467be1e1c1c2f721e8ebd73c",
"sha256:ea1af050b3e06eb73e4470f704d23007307bc0e87c13e015f6b90460f1407bd3"
"sha256:104475dc4d57bbae49017aea16fffbb763204fa2d6a70f1f3cc79962c1a383a4",
"sha256:cde472372e030e1e0bc64dac00fb53e6c095d7ab641f4281e2c995e85e205d8b"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.1"
"version": "==2.0.2"
},
"gunicorn": {
"hashes": [
@ -1151,11 +1151,11 @@
},
"typing-extensions": {
"hashes": [
"sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
"sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
"sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
],
"version": "==3.10.0.0"
"version": "==3.10.0.2"
},
"ua-parser": {
"hashes": [
@ -1294,20 +1294,20 @@
},
"xmlsec": {
"hashes": [
"sha256:23f209260b37bdc2fd96af837494c47dd1e67964f077442b63acd83c0f62e212",
"sha256:4fb38ab0bf3e47cbae136119674a869e09d61c939b510350f369c8ac46087373",
"sha256:705ab5b848afdf3a5c78b1322276054c885f44dc51601e14cb883a9c86cbe20f",
"sha256:843d10bba4c480609da74ee11fff1ee0fc1c12821c656979f12a7a4ecb043e03",
"sha256:86d54b93f8278e2f0c504d0744e39a483c1c7ce9993f2ca70184cc7770faa982",
"sha256:8922fba55a060ee81de4a7f5efc593c5bf121047763aecf0eead02e061c9d2db",
"sha256:c7b49d4fce83186b89f7ce6cec765245d36a70d0acc2f3ed0ba95c735b3667da",
"sha256:cd2eaaff7f31784a07dd99ce81fa767313df3ba1834faa4143ee2c07000cac7a",
"sha256:dea5bef9b5830c36ccb7a68a0d94d49eaea4d03fbbd04179652bf661b7e6e30f",
"sha256:eadff662d89c80db409c69d82eb3e695e16d4a5e8ab56b5b22670a54e9c6ff20",
"sha256:ee233d0bc27fb8f447ca2622b0de2ac2df45b8795f02ef263825912011fe4fe9"
"sha256:135724cdce60e6bbd072fca6f09a21f72e2cecc59eebb4eed7740c316ecabc7b",
"sha256:1b4377f6d37ad714ba95a227ef40fb54ba1b22ef5170ce04c330fe45ee6ad184",
"sha256:2c86ac6ce570c9e04f04da0cd5e7d3db346e4b5b1d006311606368f17c756ef9",
"sha256:4e5f565de311afa33aaee4724566e685f951afe301212b6cf82f98cf9d8a1749",
"sha256:9a2b8a780093b0fe8cecae53a81a8cd9edd50c08980d374c5317c91f065042d9",
"sha256:ce9c681adbc87b4f06c2b16725d9b2edbdbd508117dae4288b5faf78c1406038",
"sha256:d22da4d3dcc559fb2e54e782f39c9ddad5f8d5b356f86a79bbb80b0a45115c97",
"sha256:db3e18ca883c01bbe28c9f5197c66f676c9772cf2d85f667e6122fc4d0702225",
"sha256:e4783f7814aa2a3e318385cce8ef87c82954b9a59535a48f67da4e2c21c08ce1",
"sha256:f32e54065f0404ceff71388daa7fa7df10e1fb800051dfe302d63abb0acf0020",
"sha256:f5d242b1a19a36078608f5d7f4d561c5ca55cac8061a323a071c06275267dc19"
],
"index": "pypi",
"version": "==1.3.11"
"version": "==1.3.12"
},
"yarl": {
"hashes": [
@ -1420,11 +1420,11 @@
},
"astroid": {
"hashes": [
"sha256:b6c2d75cd7c2982d09e7d41d70213e863b3ba34d3bd4014e08f167cee966e99e",
"sha256:ecc50f9b3803ebf8ea19aa2c6df5622d8a5c31456a53c741d3be044d96ff0948"
"sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c",
"sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"
],
"markers": "python_version ~= '3.6'",
"version": "==2.7.2"
"version": "==2.7.3"
},
"attrs": {
"hashes": [
@ -1652,19 +1652,19 @@
},
"platformdirs": {
"hashes": [
"sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c",
"sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"
"sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f",
"sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"
],
"markers": "python_version >= '3.6'",
"version": "==2.2.0"
"version": "==2.3.0"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
"hashes": [
@ -1707,11 +1707,11 @@
},
"pytest": {
"hashes": [
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
],
"index": "pypi",
"version": "==6.2.4"
"version": "==6.2.5"
},
"pytest-django": {
"hashes": [
@ -1758,49 +1758,49 @@
},
"regex": {
"hashes": [
"sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd",
"sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642",
"sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1",
"sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321",
"sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529",
"sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36",
"sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a",
"sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30",
"sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce",
"sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376",
"sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd",
"sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586",
"sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7",
"sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9",
"sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea",
"sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94",
"sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3",
"sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f",
"sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267",
"sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc",
"sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23",
"sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882",
"sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc",
"sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe",
"sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759",
"sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456",
"sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239",
"sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb",
"sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948",
"sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0",
"sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183",
"sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92",
"sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade",
"sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044",
"sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee",
"sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033",
"sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2",
"sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5",
"sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2",
"sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504",
"sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
],
"version": "==2021.8.21"
"version": "==2021.8.28"
},
"requests": {
"hashes": [

View File

@ -4,14 +4,15 @@
---
[![](https://img.shields.io/discord/809154715984199690?label=Discord&style=for-the-badge)](https://discord.gg/jg33eMhnj6)
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/6?style=for-the-badge)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
[![Tests](https://img.shields.io/azure-devops/tests/beryjuorg/authentik/6?compact_message&style=for-the-badge)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
[![Join Discord](https://img.shields.io/discord/809154715984199690?label=Discord&style=for-the-badge)](https://discord.gg/jg33eMhnj6)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/goauthentik/authentik/authentik-ci-main?label=core%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-main.yml)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/goauthentik/authentik/authentik-ci-outpost?label=outpost%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-outpost.yml)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/goauthentik/authentik/authentik-ci-web?label=web%20build&style=for-the-badge)](https://github.com/goauthentik/authentik/actions/workflows/ci-web.yml)
[![Code Coverage](https://img.shields.io/codecov/c/gh/goauthentik/authentik?style=for-the-badge)](https://codecov.io/gh/goauthentik/authentik)
[![Testspace tests](https://img.shields.io/testspace/total/goauthentik/goauthentik:authentik/master?style=for-the-badge)](https://goauthentik.testspace.com/)
![Docker pulls](https://img.shields.io/docker/pulls/beryju/authentik.svg?style=for-the-badge)
![Latest version](https://img.shields.io/docker/v/beryju/authentik?sort=semver&style=for-the-badge)
![LGTM Grade](https://img.shields.io/lgtm/grade/python/github/goauthentik/authentik?style=for-the-badge)
[Transifex](https://www.transifex.com/beryjuorg/authentik/)
[![](https://img.shields.io/badge/Help%20translate-transifex-blue?style=for-the-badge)](https://www.transifex.com/beryjuorg/authentik/)
## What is authentik?

View File

@ -6,9 +6,8 @@
| Version | Supported |
| ---------- | ------------------ |
| 2021.5.x | :white_check_mark: |
| 2021.6.x | :white_check_mark: |
| 2021.7.x | :white_check_mark: |
| 2021.8.x | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.8.1"
__version__ = "2021.8.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -1,8 +1,10 @@
"""authentik api urls"""
from django.urls import include, path
from authentik.api.v2.urls import urlpatterns as v2_urls
from authentik.api.v3.urls import urlpatterns as v3_urls
urlpatterns = [
path("v2beta/", include(v2_urls)),
# Remove in 2022.1
path("v2beta/", include(v3_urls)),
path("v3/", include(v3_urls)),
]

View File

@ -4,16 +4,44 @@ from json import loads
from django.conf import settings
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.views.generic.base import View
from requests import post
from requests.exceptions import RequestException
from rest_framework.authentication import SessionAuthentication
from rest_framework.parsers import BaseParser
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from authentik.lib.config import CONFIG
class SentryTunnelView(View):
class PlainTextParser(BaseParser):
"""Plain text parser."""
media_type = "text/plain"
def parse(self, stream, media_type=None, parser_context=None) -> str:
"""Simply return a string representing the body of the request."""
return stream.read()
class CsrfExemptSessionAuthentication(SessionAuthentication):
"""CSRF-exempt Session authentication"""
def enforce_csrf(self, request: Request):
return # To not perform the csrf check previously happening
class SentryTunnelView(APIView):
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
serializer_class = None
parser_classes = [PlainTextParser]
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
authentication_classes = [CsrfExemptSessionAuthentication]
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled

View File

@ -1,6 +1,6 @@
"""api v2 urls"""
"""api v3 urls"""
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import cache_page
from drf_spectacular.views import SpectacularAPIView
from rest_framework import routers
@ -10,8 +10,8 @@ from authentik.admin.api.system import SystemView
from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionView
from authentik.admin.api.workers import WorkerView
from authentik.api.v2.config import ConfigView
from authentik.api.v2.sentry import SentryTunnelView
from authentik.api.v3.config import ConfigView
from authentik.api.v3.sentry import SentryTunnelView
from authentik.api.views import APIBrowserView
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
@ -225,7 +225,7 @@ urlpatterns = (
FlowExecutorView.as_view(),
name="flow-executor",
),
path("sentry/", csrf_exempt(SentryTunnelView.as_view()), name="sentry"),
path("schema/", SpectacularAPIView.as_view(), name="schema"),
path("sentry/", SentryTunnelView.as_view(), name="sentry"),
path("schema/", cache_page(86400)(SpectacularAPIView.as_view()), name="schema"),
]
)

View File

@ -67,7 +67,7 @@ class ApplicationSerializer(ModelSerializer):
class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Application Viewset"""
queryset = Application.objects.all()
queryset = Application.objects.all().prefetch_related("provider")
serializer_class = ApplicationSerializer
search_fields = [
"name",

View File

@ -81,7 +81,7 @@ class GroupFilter(FilterSet):
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""
queryset = Group.objects.all()
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_class = GroupFilter

View File

@ -23,7 +23,7 @@ from authentik.managed.api import ManagedSerializer
class TokenSerializer(ManagedSerializer, ModelSerializer):
"""Token Serializer"""
user_obj = UserSerializer(required=False)
user_obj = UserSerializer(required=False, source="user")
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure only API or App password tokens are created."""

View File

@ -1,8 +1,13 @@
"""Notification API Views"""
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
@ -53,3 +58,18 @@ class NotificationViewSet(
]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: OpenApiResponse(description="Marked tasks as read successfully."),
},
)
@action(detail=False, methods=["post"])
def mark_all_seen(self, request: Request) -> Response:
"""Mark all the user's notifications as seen"""
notifications = Notification.objects.filter(user=request.user)
for notification in notifications:
notification.seen = True
Notification.objects.bulk_update(notifications, ["seen"])
return Response({}, status=204)

View File

@ -1,10 +1,7 @@
"""authentik events app"""
from datetime import timedelta
from importlib import import_module
from django.apps import AppConfig
from django.db import ProgrammingError
from django.utils.timezone import now
class AuthentikEventsConfig(AppConfig):
@ -16,12 +13,3 @@ class AuthentikEventsConfig(AppConfig):
def ready(self):
import_module("authentik.events.signals")
try:
from authentik.events.models import Event
date_from = now() - timedelta(days=1)
for event in Event.objects.filter(created__gte=date_from):
event._set_prom_metrics()
except ProgrammingError:
pass

View File

@ -10,7 +10,6 @@ from django.db import models
from django.http import HttpRequest
from django.utils.timezone import now
from django.utils.translation import gettext as _
from prometheus_client import Gauge
from requests import RequestException, post
from structlog.stdlib import get_logger
@ -28,11 +27,6 @@ from authentik.tenants.models import Tenant
from authentik.tenants.utils import DEFAULT_TENANT
LOGGER = get_logger("authentik.events")
GAUGE_EVENTS = Gauge(
"authentik_events",
"Events in authentik",
["action", "user_username", "app", "client_ip"],
)
def default_event_duration():
@ -182,14 +176,6 @@ class Event(ExpiringModel):
return
self.context["geo"] = city
def _set_prom_metrics(self):
GAUGE_EVENTS.labels(
action=self.action,
user_username=self.user.get("username"),
app=self.app,
client_ip=self.client_ip,
).set(self.created.timestamp())
def save(self, *args, **kwargs):
if self._state.adding:
LOGGER.debug(
@ -200,7 +186,6 @@ class Event(ExpiringModel):
user=self.user,
)
super().save(*args, **kwargs)
self._set_prom_metrics()
@property
def summary(self) -> str:

View File

@ -11,6 +11,7 @@ from django.core.cache import cache
from prometheus_client import Gauge
from authentik.events.models import Event, EventAction
from authentik.lib.utils.errors import exception_to_string
GAUGE_TASKS = Gauge(
"authentik_system_tasks",
@ -174,9 +175,7 @@ class MonitoredTask(Task):
).save(self.result_timeout_hours)
Event.new(
EventAction.SYSTEM_TASK_EXCEPTION,
message=(
f"Task {self.__name__} encountered an error: " "\n".join(self._result.messages)
),
message=(f"Task {self.__name__} encountered an error: {exception_to_string(exc)}"),
).save()
return super().on_failure(exc, task_id, args, kwargs, einfo=einfo)

View File

@ -4,15 +4,15 @@ from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.events.models import Event, EventAction, Notification, NotificationSeverity
class TestEventsAPI(APITestCase):
"""Test Event API"""
def setUp(self) -> None:
user = User.objects.get(username="akadmin")
self.client.force_login(user)
self.user = User.objects.get(username="akadmin")
self.client.force_login(self.user)
def test_top_n(self):
"""Test top_per_user"""
@ -30,3 +30,14 @@ class TestEventsAPI(APITestCase):
reverse("authentik_api:event-actions"),
)
self.assertEqual(response.status_code, 200)
def test_notifications(self):
"""Test notifications"""
notification = Notification.objects.create(
user=self.user, severity=NotificationSeverity.ALERT, body="", seen=False
)
self.client.post(
reverse("authentik_api:notification-mark-all-seen"),
)
notification.refresh_from_db()
self.assertTrue(notification.seen)

View File

@ -31,6 +31,7 @@ class FlowPlanProcess(PROCESS_CLASS): # pragma: no cover
self.request = RequestFactory().get("/")
def run(self):
"""Execute 1000 flow plans"""
print(f"Proc {self.index} Running")
def test_inner():

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.6 on 2021-08-30 14:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0023_alter_flow_background"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="compatibility_mode",
field=models.BooleanField(
default=False,
help_text="Enable compatibility mode, increases compatibility with password managers on mobile devices.",
),
),
]

View File

@ -125,7 +125,7 @@ class Flow(SerializerModel, PolicyBindingModel):
)
compatibility_mode = models.BooleanField(
default=True,
default=False,
help_text=_(
"Enable compatibility mode, increases compatibility with "
"password managers on mobile devices."

View File

@ -2,10 +2,10 @@
from unittest.mock import MagicMock, PropertyMock, patch
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -37,7 +37,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse):
TO_STAGE_RESPONSE_MOCK = MagicMock(side_effect=to_stage_response)
class TestFlowExecutor(TestCase):
class TestFlowExecutor(APITestCase):
"""Test views logic"""
def setUp(self):

View File

@ -1,5 +1,5 @@
"""flow views tests"""
from django.test import Client, TestCase
from django.test import TestCase
from django.urls import reverse
from authentik.flows.models import Flow, FlowDesignation
@ -10,9 +10,6 @@ from authentik.flows.views import SESSION_KEY_PLAN
class TestHelperView(TestCase):
"""Test helper views logic"""
def setUp(self):
self.client = Client()
def test_default_view(self):
"""Test that ToDefaultFlow returns the expected URL"""
flow = Flow.objects.filter(

View File

@ -15,7 +15,7 @@ from authentik.core.channels import AuthJsonConsumer
from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState
GAUGE_OUTPOSTS_CONNECTED = Gauge(
"authentik_outposts_connected", "Currently connected outposts", ["outpost", "uid"]
"authentik_outposts_connected", "Currently connected outposts", ["outpost", "uid", "expected"]
)
GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
"authentik_outposts_last_update",
@ -76,6 +76,7 @@ class OutpostConsumer(AuthJsonConsumer):
GAUGE_OUTPOSTS_CONNECTED.labels(
outpost=self.outpost.name,
uid=self.last_uid,
expected=self.outpost.config.kubernetes_replicas,
).dec()
LOGGER.debug(
"removed outpost instance from cache",
@ -100,6 +101,7 @@ class OutpostConsumer(AuthJsonConsumer):
GAUGE_OUTPOSTS_CONNECTED.labels(
outpost=self.outpost.name,
uid=self.last_uid,
expected=self.outpost.config.kubernetes_replicas,
).inc()
LOGGER.debug(
"added outpost instace to cache",

View File

@ -29,7 +29,9 @@ class DockerController(BaseController):
raise ControllerException from exc
def _get_labels(self) -> dict[str, str]:
return {}
return {
"io.goauthentik.outpost-uuid": self.outpost.pk.hex,
}
def _get_env(self) -> dict[str, str]:
return {
@ -49,6 +51,17 @@ class DockerController(BaseController):
return True
return False
def _comp_labels(self, container: Container) -> bool:
"""Check if container's labels is equal to what we would set. Return true if container needs
to be rebuilt."""
should_be = self._get_labels()
for key, expected_value in should_be.items():
if key not in container.labels:
return True
if container.labels[key] != expected_value:
return True
return False
def _comp_ports(self, container: Container) -> bool:
"""Check that the container has the correct ports exposed. Return true if container needs
to be rebuilt."""
@ -92,9 +105,11 @@ class DockerController(BaseController):
"environment": self._get_env(),
"labels": self._get_labels(),
"restart_policy": {"Name": "unless-stopped"},
"network": self.outpost.config.docker_network,
}
if settings.TEST:
del container_args["ports"]
del container_args["network"]
container_args["network_mode"] = "host"
return (
self.client.containers.create(**container_args),
@ -133,6 +148,11 @@ class DockerController(BaseController):
self.logger.info("Container has outdated config, re-creating...")
self.down()
return self.up(depth + 1)
# Check that container values match our values
if self._comp_labels(container):
self.logger.info("Container has outdated labels, re-creating...")
self.down()
return self.up(depth + 1)
if (
container.attrs.get("HostConfig", {})
.get("RestartPolicy", {})

View File

@ -3,10 +3,11 @@ from typing import TYPE_CHECKING, Generic, TypeVar
from django.utils.text import slugify
from kubernetes.client import V1ObjectMeta
from kubernetes.client.exceptions import ApiException, OpenApiException
from kubernetes.client.models.v1_deployment import V1Deployment
from kubernetes.client.models.v1_pod import V1Pod
from kubernetes.client.rest import ApiException
from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import __version__
from authentik.lib.sentry import SentryIgnoredException
@ -72,8 +73,9 @@ class KubernetesObjectReconciler(Generic[T]):
try:
try:
current = self.retrieve()
except ApiException as exc:
if exc.status == 404:
except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 404:
self.logger.debug("Failed to get current, triggering recreate")
raise NeedsRecreate from exc
self.logger.debug("Other unhandled error", exc=exc)
@ -104,8 +106,9 @@ class KubernetesObjectReconciler(Generic[T]):
current = self.retrieve()
self.delete(current)
self.logger.debug("Removing")
except ApiException as exc:
if exc.status == 404:
except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 404:
self.logger.debug("Failed to get current, assuming non-existant")
return
self.logger.debug("Other unhandled error", exc=exc)

View File

@ -3,8 +3,9 @@ from io import StringIO
from typing import Type
from kubernetes.client.api_client import ApiClient
from kubernetes.client.exceptions import ApiException
from kubernetes.client.exceptions import OpenApiException
from structlog.testing import capture_logs
from urllib3.exceptions import HTTPError
from yaml import dump_all
from authentik.outposts.controllers.base import BaseController, ControllerException
@ -12,7 +13,7 @@ from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.secret import SecretReconciler
from authentik.outposts.controllers.k8s.service import ServiceReconciler
from authentik.outposts.models import KubernetesServiceConnection, Outpost
from authentik.outposts.models import KubernetesServiceConnection, Outpost, ServiceConnectionInvalid
class KubernetesController(BaseController):
@ -40,7 +41,7 @@ class KubernetesController(BaseController):
reconciler = self.reconcilers[reconcile_key](self)
reconciler.up()
except ApiException as exc:
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def up_with_logs(self) -> list[str]:
@ -55,7 +56,7 @@ class KubernetesController(BaseController):
reconciler.up()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
return all_logs
except ApiException as exc:
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def down(self):
@ -65,7 +66,7 @@ class KubernetesController(BaseController):
self.logger.debug("Tearing down object", name=reconcile_key)
reconciler.down()
except ApiException as exc:
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def down_with_logs(self) -> list[str]:
@ -80,7 +81,7 @@ class KubernetesController(BaseController):
reconciler.down()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
return all_logs
except ApiException as exc:
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc
def get_static_deployment(self) -> str:

View File

@ -56,6 +56,7 @@ class ServiceConnectionInvalid(SentryIgnoredException):
@dataclass
# pylint: disable=too-many-instance-attributes
class OutpostConfig:
"""Configuration an outpost uses to configure it self"""
@ -67,8 +68,10 @@ class OutpostConfig:
log_level: str = CONFIG.y("log_level")
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
error_reporting_environment: str = CONFIG.y("error_reporting.environment", "customer")
object_naming_template: str = field(default="ak-outpost-%(name)s")
docker_network: Optional[str] = field(default=None)
kubernetes_replicas: int = field(default=1)
kubernetes_namespace: str = field(default_factory=get_namespace)
kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
@ -362,7 +365,7 @@ class Outpost(ManagedModel):
)
try:
assign_perm(code_name, user, model_or_perm)
except Permission.DoesNotExist as exc:
except (Permission.DoesNotExist, AttributeError) as exc:
LOGGER.warning(
"permission doesn't exist",
code_name=code_name,

View File

@ -8,8 +8,11 @@ from structlog.stdlib import get_logger
from authentik.policies.models import Policy
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
RE_LOWER = re.compile("[a-z]")
RE_UPPER = re.compile("[A-Z]")
class PasswordPolicy(Policy):
@ -38,31 +41,42 @@ class PasswordPolicy(Policy):
return "ak-policy-password-form"
def passes(self, request: PolicyRequest) -> PolicyResult:
if self.password_field not in request.context:
if (
self.password_field not in request.context
and self.password_field not in request.context.get(PLAN_CONTEXT_PROMPT, {})
):
LOGGER.warning(
"Password field not set in Policy Request",
field=self.password_field,
fields=request.context.keys(),
prompt_fields=request.context.get(PLAN_CONTEXT_PROMPT, {}).keys(),
)
return PolicyResult(False, _("Password not set in context"))
password = request.context[self.password_field]
filter_regex = []
if self.amount_lowercase > 0:
filter_regex.append(r"[a-z]{%d,}" % self.amount_lowercase)
if self.amount_uppercase > 0:
filter_regex.append(r"[A-Z]{%d,}" % self.amount_uppercase)
if self.password_field in request.context:
password = request.context[self.password_field]
else:
password = request.context[PLAN_CONTEXT_PROMPT][self.password_field]
if len(password) < self.length_min:
LOGGER.debug("password failed", reason="length")
return PolicyResult(False, self.error_message)
if self.amount_lowercase > 0 and len(RE_LOWER.findall(password)) < self.amount_lowercase:
LOGGER.debug("password failed", reason="amount_lowercase")
return PolicyResult(False, self.error_message)
if self.amount_uppercase > 0 and len(RE_UPPER.findall(password)) < self.amount_lowercase:
LOGGER.debug("password failed", reason="amount_uppercase")
return PolicyResult(False, self.error_message)
if self.amount_symbols > 0:
filter_regex.append(r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols))
full_regex = "|".join(filter_regex)
LOGGER.debug("Built regex", regexp=full_regex)
result = bool(re.compile(full_regex).match(password))
count = 0
for symbol in self.symbol_charset:
count += password.count(symbol)
if count < self.amount_symbols:
LOGGER.debug("password failed", reason="amount_symbols")
return PolicyResult(False, self.error_message)
result = result and len(password) >= self.length_min
if not result:
return PolicyResult(result, self.error_message)
return PolicyResult(result)
return PolicyResult(True)
class Meta:

View File

@ -1,57 +0,0 @@
"""Password Policy tests"""
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.policies.password.models import PasswordPolicy
from authentik.policies.types import PolicyRequest, PolicyResult
class TestPasswordPolicy(TestCase):
"""Test Password Policy"""
def test_invalid(self):
"""Test without password"""
policy = PasswordPolicy.objects.create(
name="test_invalid",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=24,
error_message="test message",
)
request = PolicyRequest(get_anonymous_user())
result: PolicyResult = policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages[0], "Password not set in context")
def test_false(self):
"""Failing password case"""
policy = PasswordPolicy.objects.create(
name="test_false",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=24,
error_message="test message",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "test"
result: PolicyResult = policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_true(self):
"""Positive password case"""
policy = PasswordPolicy.objects.create(
name="test_true",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=3,
error_message="test message",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "Test()!"
result: PolicyResult = policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -0,0 +1,80 @@
"""Password flow tests"""
from django.urls.base import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.policies.password.models import PasswordPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
class TestPasswordPolicyFlow(APITestCase):
"""Test Password Policy"""
def setUp(self) -> None:
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.flow = Flow.objects.create(
name="test-prompt",
slug="test-prompt",
designation=FlowDesignation.AUTHENTICATION,
)
password_prompt = Prompt.objects.create(
field_key="password",
label="PASSWORD_LABEL",
type=FieldTypes.PASSWORD,
required=True,
placeholder="PASSWORD_PLACEHOLDER",
)
self.policy = PasswordPolicy.objects.create(
name="test_true",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=3,
error_message="test message",
)
stage = PromptStage.objects.create(name="prompt-stage")
stage.validation_policies.set([self.policy])
stage.fields.set(
[
password_prompt,
]
)
FlowStageBinding.objects.create(target=self.flow, stage=stage, order=2)
def test_prompt_data(self):
"""Test policy attached to a prompt stage"""
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
{"password": "akadmin"},
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-prompt",
"fields": [
{
"field_key": "password",
"label": "PASSWORD_LABEL",
"order": 0,
"placeholder": "PASSWORD_PLACEHOLDER",
"required": True,
"type": "password",
}
],
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"response_errors": {
"non_field_errors": [{"code": "invalid", "string": self.policy.error_message}]
},
"type": ChallengeTypes.NATIVE.value,
},
)

View File

@ -0,0 +1,68 @@
"""Password Policy tests"""
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.lib.generators import generate_key
from authentik.policies.password.models import PasswordPolicy
from authentik.policies.types import PolicyRequest, PolicyResult
class TestPasswordPolicy(TestCase):
"""Test Password Policy"""
def setUp(self) -> None:
self.policy = PasswordPolicy.objects.create(
name="test_false",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=24,
error_message="test message",
)
def test_invalid(self):
"""Test without password"""
request = PolicyRequest(get_anonymous_user())
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages[0], "Password not set in context")
def test_failed_length(self):
"""Password too short"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "test"
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_lowercase(self):
"""not enough lowercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe"
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_uppercase(self):
"""not enough uppercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "tttttttttttttttttttttttE"
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_symbols(self):
"""not enough uppercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TETETETETETETETETETETETETe!!!"
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_true(self):
"""Positive password case"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = generate_key() + "ee!!!"
result: PolicyResult = self.policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -29,7 +29,19 @@ class LDAPProviderViewSet(UsedByMixin, ModelViewSet):
queryset = LDAPProvider.objects.all()
serializer_class = LDAPProviderSerializer
filterset_fields = "__all__"
filterset_fields = {
"application": ["isnull"],
"name": ["iexact"],
"authorization_flow__slug": ["iexact"],
"base_dn": ["iexact"],
"search_group__group_uuid": ["iexact"],
"search_group__name": ["iexact"],
"certificate__kp_uuid": ["iexact"],
"certificate__name": ["iexact"],
"tls_server_name": ["iexact"],
"uid_start_number": ["iexact"],
"gid_start_number": ["iexact"],
}
ordering = ["name"]

View File

@ -80,7 +80,24 @@ class ProxyProviderViewSet(UsedByMixin, ModelViewSet):
queryset = ProxyProvider.objects.all()
serializer_class = ProxyProviderSerializer
filterset_fields = "__all__"
filterset_fields = {
"application": ["isnull"],
"name": ["iexact"],
"authorization_flow__slug": ["iexact"],
"property_mappings": ["iexact"],
"internal_host": ["iexact"],
"external_host": ["iexact"],
"internal_host_ssl_validation": ["iexact"],
"certificate__kp_uuid": ["iexact"],
"certificate__name": ["iexact"],
"skip_path_regex": ["iexact"],
"basic_auth_enabled": ["iexact"],
"basic_auth_password_attribute": ["iexact"],
"basic_auth_user_attribute": ["iexact"],
"mode": ["iexact"],
"redirect_uris": ["iexact"],
"cookie_domain": ["iexact"],
}
ordering = ["name"]

View File

@ -22,13 +22,13 @@ class ProxyDockerController(DockerController):
for proxy_provider in ProxyProvider.objects.filter(outpost__in=[self.outpost]):
proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host)
hosts.append(f"`{external_host_name}`")
hosts.append(f"`{external_host_name.netloc}`")
traefik_name = f"ak-outpost-{self.outpost.pk.hex}"
return {
"traefik.enable": "true",
f"traefik.http.routers.{traefik_name}-router.rule": f"Host({','.join(hosts)})",
f"traefik.http.routers.{traefik_name}-router.tls": "true",
f"traefik.http.routers.{traefik_name}-router.service": f"{traefik_name}-service",
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path": "/",
f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port": "4180",
}
labels = super()._get_labels()
labels["traefik.enable"] = "true"
labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "4180"
return labels

View File

@ -61,9 +61,10 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
have_hosts.sort()
have_hosts_tls = []
for tls_config in current.spec.tls:
if tls_config and tls_config.hosts:
have_hosts_tls += tls_config.hosts
if current.spec.tls:
for tls_config in current.spec.tls:
if tls_config and tls_config.hosts:
have_hosts_tls += tls_config.hosts
have_hosts_tls.sort()
if have_hosts != expected_hosts:

View File

@ -96,6 +96,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
def get_reference_object(self) -> TraefikMiddleware:
"""Get deployment object for outpost"""
port = 9000 if self.is_embedded else 4180
return TraefikMiddleware(
apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
kind="Middleware",
@ -106,7 +107,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
),
spec=TraefikMiddlewareSpec(
forwardAuth=TraefikMiddlewareSpecForwardAuth(
address=f"http://{self.name}.{self.namespace}:4180/akprox/auth?traefik",
address=f"http://{self.name}.{self.namespace}:{port}/akprox/auth?traefik",
authResponseHeaders=[
"Set-Cookie",
"X-Auth-Username",

View File

@ -19,22 +19,24 @@ class ASGILogger:
app: ASGIApp
status_code: int
start: float
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
content_length = 0
status_code = 0
request_id = ""
location = ""
start = time()
async def send_hooked(message: Message) -> None:
"""Hooked send method, which records status code and content-length, and for the final
requests logs it"""
headers = dict(message.get("headers", []))
if "status" in message:
self.status_code = message["status"]
nonlocal status_code
status_code = message["status"]
if b"Content-Length" in headers:
nonlocal content_length
@ -43,14 +45,19 @@ class ASGILogger:
if message["type"] == "http.response.start":
response_headers = dict(message["headers"])
nonlocal request_id
nonlocal location
request_id = response_headers.get(RESPONSE_HEADER_ID.encode(), b"").decode()
location = response_headers.get(b"Location", b"").decode()
if message["type"] == "http.response.body" and not message.get("more_body", True):
runtime = int((time() - self.start) * 1000)
self.log(scope, runtime, content_length, request_id=request_id)
nonlocal start
runtime = int((time() - start) * 1000)
kwargs = {"request_id": request_id}
if location != "":
kwargs["location"] = location
self.log(scope, runtime, content_length, status_code, **kwargs)
await send(message)
self.start = time()
if scope["type"] == "lifespan":
# https://code.djangoproject.com/ticket/31508
# https://github.com/encode/uvicorn/issues/266
@ -68,7 +75,7 @@ class ASGILogger:
# Check if header has multiple values, and use the first one
return client_ip.split(", ")[0]
def log(self, scope: Scope, content_length: int, runtime: float, **kwargs):
def log(self, scope: Scope, content_length: int, runtime: float, status_code: int, **kwargs):
"""Outpot access logs in a structured format"""
host = self._get_ip(scope)
query_string = ""
@ -79,7 +86,7 @@ class ASGILogger:
host=host,
method=scope.get("method", ""),
scheme=scope.get("scheme", ""),
status=self.status_code,
status=status_code,
size=content_length / 1000 if content_length > 0 else 0,
runtime=runtime,
**kwargs,

View File

@ -24,7 +24,7 @@ class SessionMiddleware(UpstreamSessionMiddleware):
# Since go does not consider localhost with http a secure origin
# we can't set the secure flag.
user_agent = request.META.get("HTTP_USER_AGENT", "")
if user_agent.startswith("authentik-outpost@"):
if user_agent.startswith("authentik-outpost@") or "safari" in user_agent.lower():
return False
return True
return False

View File

@ -74,6 +74,7 @@ LANGUAGE_COOKIE_NAME = f"authentik_language{_cookie_suffix}"
SESSION_COOKIE_NAME = f"authentik_session{_cookie_suffix}"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
BACKEND_INBUILT,
BACKEND_APP_PASSWORD,
BACKEND_LDAP,
@ -149,9 +150,20 @@ SPECTACULAR_SETTINGS = {
"DESCRIPTION": "Making authentication simple.",
"VERSION": __version__,
"COMPONENT_SPLIT_REQUEST": True,
"SCHEMA_PATH_PREFIX": "/api/v([0-9]+(beta)?)",
"SCHEMA_PATH_PREFIX_TRIM": True,
"SERVERS": [
{
"url": "/api/v3/",
},
{
"url": "/api/v2beta/",
},
],
"CONTACT": {
"email": "hello@beryju.org",
},
"AUTHENTICATION_WHITELIST": ["authentik.api.authentication.TokenAuthentication"],
"LICENSE": {
"name": "GNU GPLv3",
"url": "https://github.com/goauthentik/authentik/blob/master/LICENSE",
@ -179,6 +191,9 @@ REST_FRAMEWORK = {
"rest_framework.filters.OrderingFilter",
"rest_framework.filters.SearchFilter",
],
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
],
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.DjangoObjectPermissions",),
"DEFAULT_AUTHENTICATION_CLASSES": (
"authentik.api.authentication.TokenAuthentication",
@ -188,6 +203,7 @@ REST_FRAMEWORK = {
"rest_framework.renderers.JSONRenderer",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"TEST_REQUEST_DEFAULT_FORMAT": "json",
}
REDIS_PROTOCOL_PREFIX = "redis://"
@ -356,7 +372,7 @@ CELERY_RESULT_BACKEND = (
# Database backup
DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage"
DBBACKUP_STORAGE_OPTIONS = {"location": "./backups" if DEBUG else "/backups"}
DBBACKUP_FILENAME_TEMPLATE = "authentik-backup-{datetime}.sql"
DBBACKUP_FILENAME_TEMPLATE = f"authentik-backup-{__version__}-{{datetime}}.sql"
DBBACKUP_CONNECTOR_MAPPING = {
"django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector",
}
@ -371,6 +387,7 @@ if CONFIG.y("postgresql.s3_backup"):
"default_acl": "private",
"endpoint_url": CONFIG.y("postgresql.s3_backup.host"),
"location": CONFIG.y("postgresql.s3_backup.location", ""),
"verify": not CONFIG.y_bool("postgresql.s3_backup.insecure_skip_verify", False),
}
j_print(
"Database backup to S3 is configured",

View File

@ -2,17 +2,13 @@
from base64 import b64encode
from django.conf import settings
from django.test import Client, TestCase
from django.test import TestCase
from django.urls import reverse
class TestRoot(TestCase):
"""Test root application"""
def setUp(self):
super().setUp()
self.client = Client()
def test_monitoring_error(self):
"""Test monitoring without any credentials"""
response = self.client.get(reverse("metrics"))

View File

@ -43,7 +43,6 @@ class BaseOAuthClient:
profile_url = self.source.profile_url
try:
response = self.do_request("get", profile_url, token=token)
LOGGER.debug(response.text)
response.raise_for_status()
except RequestException as exc:
LOGGER.warning("Unable to fetch user profile", exc=exc)

View File

@ -65,7 +65,6 @@ class OAuth2Client(BaseOAuthClient):
data=args,
headers=self._default_headers,
)
LOGGER.debug(response.text)
response.raise_for_status()
except RequestException as exc:
LOGGER.warning("Unable to fetch access token", exc=exc)

View File

@ -1,5 +1,5 @@
"""Twitter Type tests"""
from django.test import Client, TestCase
from django.test import TestCase
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
@ -92,7 +92,6 @@ class TestTypeGitHub(TestCase):
"""OAuth Source tests"""
def setUp(self):
self.client = Client()
self.source = OAuthSource.objects.create(
name="test",
slug="test",

View File

@ -36,7 +36,6 @@ class AzureADClient(OAuth2Client):
profile_url,
headers={"Authorization": f"{token['token_type']} {token['access_token']}"},
)
LOGGER.debug(response.text)
response.raise_for_status()
except RequestException as exc:
LOGGER.warning("Unable to fetch user profile", exc=exc)

View File

@ -51,20 +51,30 @@ def get_webauthn_challenge(request: HttpRequest, device: WebAuthnDevice) -> dict
# for the reasons outlined in the comment in webauthn_begin_activate.
request.session["challenge"] = challenge.rstrip("=")
webauthn_user = WebAuthnUser(
device.user.uid,
device.user.username,
device.user.name,
device.user.avatar,
device.credential_id,
device.public_key,
device.sign_count,
device.rp_id,
)
assertion = {}
user = device.user
webauthn_assertion_options = WebAuthnAssertionOptions(webauthn_user, challenge)
# We want all the user's WebAuthn devices and merge their challenges
for user_device in WebAuthnDevice.objects.filter(user=device.user).order_by("name"):
webauthn_user = WebAuthnUser(
user.uid,
user.username,
user.name,
user.avatar,
user_device.credential_id,
user_device.public_key,
user_device.sign_count,
user_device.rp_id,
)
webauthn_assertion_options = WebAuthnAssertionOptions(webauthn_user, challenge)
if assertion == {}:
assertion = webauthn_assertion_options.assertion_dict
else:
assertion["allowCredentials"] += webauthn_assertion_options.assertion_dict.get(
"allowCredentials"
)
return webauthn_assertion_options.assertion_dict
return assertion
def validate_challenge_code(code: str, request: HttpRequest, user: User) -> str:

View File

@ -20,8 +20,6 @@ from authentik.stages.authenticator_validate.models import AuthenticatorValidate
LOGGER = get_logger()
PER_DEVICE_CLASSES = [DeviceClasses.WEBAUTHN]
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
"""Authenticator challenge"""
@ -91,9 +89,9 @@ class AuthenticatorValidateStageView(ChallengeStageView):
if device_class not in stage.device_classes:
LOGGER.debug("device class not allowed", device_class=device_class)
continue
# Ensure only classes in PER_DEVICE_CLASSES are returned per device
# otherwise only return a single challenge
if device_class in seen_classes and device_class not in PER_DEVICE_CLASSES:
# Ensure only one challenge per device class
# WebAuthn does another device loop to find all webuahtn devices
if device_class in seen_classes:
continue
if device_class not in seen_classes:
seen_classes.append(device_class)

View File

@ -1,12 +1,12 @@
"""Test validator stage"""
from unittest.mock import MagicMock, patch
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -26,7 +26,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTests(TestCase):
class AuthenticatorValidateStageTests(APITestCase):
"""Test validator stage"""
def setUp(self) -> None:

View File

@ -1,7 +1,7 @@
"""captcha tests"""
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -16,13 +16,12 @@ RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
class TestCaptchaStage(TestCase):
class TestCaptchaStage(APITestCase):
"""Captcha tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-captcha",

View File

@ -1,9 +1,9 @@
"""consent tests"""
from time import sleep
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import Application, User
from authentik.core.tasks import clean_expired_models
@ -15,7 +15,7 @@ from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
class TestConsentStage(TestCase):
class TestConsentStage(APITestCase):
"""Consent tests"""
def setUp(self):
@ -25,7 +25,6 @@ class TestConsentStage(TestCase):
name="test-application",
slug="test-application",
)
self.client = Client()
def test_always_required(self):
"""Test always required consent"""

View File

@ -1,7 +1,7 @@
"""deny tests"""
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -12,13 +12,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.deny.models import DenyStage
class TestUserDenyStage(TestCase):
class TestUserDenyStage(APITestCase):
"""Deny tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-logout",

View File

@ -1,7 +1,7 @@
"""dummy tests"""
from django.test import TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -9,7 +9,7 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.stages.dummy.models import DummyStage
class TestDummyStage(TestCase):
class TestDummyStage(APITestCase):
"""Dummy tests"""
def setUp(self):

View File

@ -4,8 +4,8 @@ from unittest.mock import MagicMock, patch
from django.core import mail
from django.core.mail.backends.locmem import EmailBackend
from django.test import Client, TestCase
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.events.models import Event, EventAction
@ -16,13 +16,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage
class TestEmailStageSending(TestCase):
class TestEmailStageSending(APITestCase):
"""Email tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-email",

View File

@ -2,10 +2,10 @@
from unittest.mock import MagicMock, patch
from django.core import mail
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
from rest_framework.test import APITestCase
from authentik.core.models import Token, User
from authentik.flows.challenge import ChallengeTypes
@ -17,13 +17,12 @@ from authentik.stages.email.models import EmailStage
from authentik.stages.email.stage import QS_KEY_TOKEN
class TestEmailStage(TestCase):
class TestEmailStage(APITestCase):
"""Email tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-email",

View File

@ -96,7 +96,9 @@ class IdentificationChallengeResponse(ChallengeResponse):
# No password stage select, don't validate the password
return attrs
password = attrs["password"]
password = attrs.get("password", None)
if not password:
LOGGER.warning("Password not set for ident+auth attempt")
try:
user = authenticate(
self.stage.request,
@ -132,6 +134,9 @@ class IdentificationStageView(ChallengeStageView):
else:
model_field += "__exact"
query |= Q(**{model_field: uid_value})
if not query:
LOGGER.debug("Empty user query", query=query)
return None
users = User.objects.filter(query, is_active=True)
if users.exists():
LOGGER.debug("Found user", user=users.first(), query=query)

View File

@ -1,7 +1,7 @@
"""identification tests"""
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -13,7 +13,7 @@ from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
class TestIdentificationStage(TestCase):
class TestIdentificationStage(APITestCase):
"""Identification tests"""
def setUp(self):
@ -22,7 +22,6 @@ class TestIdentificationStage(TestCase):
self.user = User.objects.create_user(
username="unittest", email="test@beryju.org", password=self.password
)
self.client = Client()
# OAuthSource for the login view
source = OAuthSource.objects.create(name="test", slug="test")
@ -137,6 +136,48 @@ class TestIdentificationStage(TestCase):
)
self.assertEqual(response.status_code, 200)
def test_invalid_no_fields(self):
"""Test invalid with username (no user fields are enabled)"""
self.stage.user_fields = []
self.stage.save()
form_data = {"uid_field": self.user.username}
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
form_data,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": False,
"primary_action": "Log in",
"response_errors": {
"non_field_errors": [
{"code": "invalid", "string": "Failed to " "authenticate."}
]
},
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"sources": [
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/.svg",
"name": "test",
}
],
"user_fields": [],
},
)
def test_invalid_with_invalid_email(self):
"""Test with invalid email (user doesn't exist) -> Will return to login form"""
form_data = {"uid_field": self.user.email + "test"}

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.6 on 2021-09-01 12:11
from django.db import migrations, models
import authentik.core.models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_invitation", "0004_invitation_single_use"),
]
operations = [
migrations.AddField(
model_name="invitation",
name="expiring",
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name="invitation",
name="expires",
field=models.DateTimeField(default=authentik.core.models.default_token_duration),
),
]

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from authentik.core.models import User
from authentik.core.models import ExpiringModel, User
from authentik.flows.models import Stage
@ -48,7 +48,7 @@ class InvitationStage(Stage):
verbose_name_plural = _("Invitation Stages")
class Invitation(models.Model):
class Invitation(ExpiringModel):
"""Single-use invitation link"""
invite_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -59,7 +59,6 @@ class Invitation(models.Model):
)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
expires = models.DateTimeField(default=None, blank=True, null=True)
fixed_data = models.JSONField(
default=dict,
blank=True,

View File

@ -1,7 +1,6 @@
"""invitation tests"""
from unittest.mock import MagicMock, patch
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
@ -21,13 +20,12 @@ from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
class TestUserLoginStage(TestCase):
class TestUserLoginStage(APITestCase):
"""Login tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-invitation",

View File

@ -32,9 +32,7 @@ PLAN_CONTEXT_METHOD_ARGS = "auth_method_args"
SESSION_INVALID_TRIES = "user_invalid_tries"
def authenticate(
request: HttpRequest, backends: list[str], **credentials: dict[str, Any]
) -> Optional[User]:
def authenticate(request: HttpRequest, backends: list[str], **credentials: Any) -> Optional[User]:
"""If the given credentials are valid, return a User object.
Customized version of django's authenticate, which accepts a list of backends"""

View File

@ -2,9 +2,9 @@
from unittest.mock import MagicMock, patch
from django.core.exceptions import PermissionDenied
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -20,7 +20,7 @@ from authentik.stages.password.models import PasswordStage
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
class TestPasswordStage(TestCase):
class TestPasswordStage(APITestCase):
"""Password tests"""
def setUp(self):
@ -29,7 +29,6 @@ class TestPasswordStage(TestCase):
self.user = User.objects.create_user(
username="unittest", email="test@beryju.org", password=self.password
)
self.client = Client()
self.flow = Flow.objects.create(
name="test-password",

View File

@ -1,10 +1,10 @@
"""Prompt tests"""
from unittest.mock import MagicMock, patch
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.exceptions import ErrorDetail
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -17,13 +17,12 @@ from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT, PromptChallengeResponse
class TestPromptStage(TestCase):
class TestPromptStage(APITestCase):
"""Prompt tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-prompt",
@ -97,7 +96,6 @@ class TestPromptStage(TestCase):
static_prompt,
]
)
self.stage.save()
self.prompt_data = {
username_prompt.field_key: "test-username",

View File

@ -1,9 +1,9 @@
"""delete tests"""
from unittest.mock import patch
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -15,14 +15,13 @@ from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.user_delete.models import UserDeleteStage
class TestUserDeleteStage(TestCase):
class TestUserDeleteStage(APITestCase):
"""Delete tests"""
def setUp(self):
super().setUp()
self.username = "qerqwerqrwqwerwq"
self.user = User.objects.create(username=self.username, email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-delete",

View File

@ -2,9 +2,9 @@
from time import sleep
from unittest.mock import patch
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -16,13 +16,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.user_login.models import UserLoginStage
class TestUserLoginStage(TestCase):
class TestUserLoginStage(APITestCase):
"""Login tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-login",

View File

@ -1,7 +1,7 @@
"""logout tests"""
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
@ -14,13 +14,12 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.user_logout.models import UserLogoutStage
class TestUserLogoutStage(TestCase):
class TestUserLogoutStage(APITestCase):
"""Logout tests"""
def setUp(self):
super().setUp()
self.user = User.objects.create(username="unittest", email="test@beryju.org")
self.client = Client()
self.flow = Flow.objects.create(
name="test-logout",

View File

@ -3,9 +3,9 @@ import string
from random import SystemRandom
from unittest.mock import patch
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import USER_ATTRIBUTE_SOURCES, Source, User, UserSourceConnection
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
@ -19,13 +19,11 @@ from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.models import UserWriteStage
class TestUserWriteStage(TestCase):
class TestUserWriteStage(APITestCase):
"""Write tests"""
def setUp(self):
super().setUp()
self.client = Client()
self.flow = Flow.objects.create(
name="test-write",
slug="test-write",

View File

@ -1,120 +0,0 @@
trigger:
batch: true
branches:
include:
- master
- next
- version-*
stages:
- stage: generate
jobs:
- job: generate_api
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
inputs:
version: '1.16.3'
- task: CmdLine@2
inputs:
script: make gen-outpost
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'api/'
artifact: 'go_api_client'
publishLocation: 'pipeline'
- stage: lint
jobs:
- job: golint
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
inputs:
version: '1.16.3'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_api_client'
path: "api/"
- task: CmdLine@2
inputs:
script: |
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci-lint run -v --timeout 200s
- stage: build_docker
jobs:
- job: proxy_build_docker
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
inputs:
version: '1.16.3'
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-proxy'
command: 'build'
Dockerfile: 'proxy.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)
gh-$(Build.SourceVersion)
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-proxy'
command: 'push'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)
gh-$(Build.SourceVersion)
- job: ldap_build_docker
pool:
vmImage: 'ubuntu-latest'
steps:
- task: GoTool@0
inputs:
version: '1.16.3'
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-ldap'
command: 'build'
Dockerfile: 'ldap.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)
gh-$(Build.SourceVersion)
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-ldap'
command: 'push'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)
gh-$(Build.SourceVersion)

View File

@ -1,426 +0,0 @@
trigger:
batch: true
branches:
include:
- master
- next
- version-*
paths:
exclude:
- website
- outpost
resources:
- repo: self
variables:
- name: POSTGRES_DB
value: authentik
- name: POSTGRES_USER
value: authentik
- name: POSTGRES_PASSWORD
value: "EK-5jnKfjrGRm<77"
- group: coverage
stages:
- stage: Lint_and_test
jobs:
- job: pylint
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run pylint authentik tests lifecycle
- job: black
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: pipenv run black --check authentik tests lifecycle
- job: isort
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: pipenv run isort --check authentik tests lifecycle
- job: bandit
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: pipenv run bandit -r authentik tests lifecycle
- job: pyright
pool:
vmImage: ubuntu-latest
steps:
- task: UseNode@1
inputs:
version: '12.x'
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: npm install -g pyright@1.1.136
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: pipenv run pyright e2e lifecycle
- job: migrations
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run python -m lifecycle.migrate
- job: migrations_from_previous_release
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
displayName: Prepare Last tagged release
inputs:
script: |
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
git checkout $(git describe --abbrev=0 --match 'version/*')
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
displayName: Migrate to last tagged release
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run python -m lifecycle.migrate
- task: CmdLine@2
displayName: Install current branch
inputs:
script: |
set -x
git checkout ${{ variables.branchName }}
pipenv sync --dev
- task: CmdLine@2
displayName: Migrate to current branch
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run python -m lifecycle.migrate
- job: coverage_unittest
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
displayName: Run full test suite
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run make test
- task: CmdLine@2
inputs:
script: |
mkdir output-unittest
mv unittest.xml output-unittest/unittest.xml
mv .coverage output-unittest/coverage
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'output-unittest/'
artifact: 'coverage-unittest'
publishLocation: 'pipeline'
- job: coverage_integration
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
displayName: Install K3d and prepare
inputs:
script: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
k3d cluster create
k3d kubeconfig write -o ~/.kube/config --overwrite
- task: CmdLine@2
displayName: Run full test suite
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run make test-integration
- task: CmdLine@2
inputs:
script: |
mkdir output-integration
mv unittest.xml output-integration/unittest.xml
mv .coverage output-integration/coverage
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'output-integration/'
artifact: 'coverage-integration'
publishLocation: 'pipeline'
- job: coverage_e2e
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: NodeTool@0
inputs:
versionSpec: '16.x'
- task: DockerCompose@0
displayName: Run services
inputs:
dockerComposeFile: 'scripts/ci.docker-compose.yml'
action: 'Run services'
buildImages: false
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev --python python3.9
- task: DockerCompose@0
displayName: Run ChromeDriver
inputs:
dockerComposeFile: 'tests/e2e/ci.docker-compose.yml'
action: 'Run a specific service'
serviceName: 'chrome'
- task: CmdLine@2
displayName: Build static files for e2e
inputs:
script: |
cd web
npm i
npm run build
- task: CmdLine@2
displayName: Run full test suite
inputs:
script: |
pipenv run python -m scripts.generate_ci_config
pipenv run make test-e2e
- task: CmdLine@2
condition: always()
displayName: Cleanup
inputs:
script: |
docker stop $(docker ps -aq)
docker container prune -f
- task: CmdLine@2
displayName: Prepare unittests and coverage for upload
inputs:
script: |
mkdir output-e2e
mv unittest.xml output-e2e/unittest.xml
mv .coverage output-e2e/coverage
- task: PublishPipelineArtifact@1
condition: failed()
displayName: Upload screenshots if selenium tests fail
inputs:
targetPath: 'selenium_screenshots/'
artifact: 'selenium screenshots'
publishLocation: 'pipeline'
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'output-e2e/'
artifact: 'coverage-e2e'
publishLocation: 'pipeline'
- stage: test_combine
jobs:
- job: test_coverage_combine
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'coverage-e2e'
path: "coverage-e2e/"
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'coverage-integration'
path: "coverage-integration/"
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'coverage-unittest'
path: "coverage-unittest/"
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt update
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
pipenv run coverage combine coverage-e2e/coverage coverage-unittest/coverage coverage-integration/coverage
pipenv run coverage xml
pipenv run coverage html
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: 'coverage.xml'
pathToSources: '$(System.DefaultWorkingDirectory)'
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: |
coverage-e2e/unittest.xml
coverage-integration/unittest.xml
coverage-unittest/unittest.xml
mergeTestResults: true
- task: CmdLine@2
inputs:
script: bash <(curl -s https://codecov.io/bash)
- stage: Build
jobs:
- job: build_server
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/server'
command: 'build'
Dockerfile: 'Dockerfile'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/server'
command: 'push'
tags: |
gh-$(branchName)
gh-$(branchName)-$(timestamp)

View File

@ -60,7 +60,9 @@ func main() {
for {
go attemptStartBackend(g)
ws.Start()
go attemptProxyStart(ws, u)
if !config.G.Web.DisableEmbeddedOutpost {
go attemptProxyStart(ws, u)
}
<-ex
running = false

View File

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

2
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/go-ldap/ldap/v3 v3.4.1
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/runtime v0.19.30
github.com/go-openapi/runtime v0.19.31
github.com/go-openapi/strfmt v0.20.2
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect

4
go.sum
View File

@ -205,8 +205,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/runtime v0.19.30 h1:bVDeSf4HU9EMth+lHD1EthaHe1SFoUVPaUvQtkGS9g8=
github.com/go-openapi/runtime v0.19.30/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/runtime v0.19.31 h1:GX+MgBxN12s/tQiHNJpvHDIoZiEXAz6j6Rqg0oJcnpg=
github.com/go-openapi/runtime v0.19.31/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=

View File

@ -27,9 +27,10 @@ type RedisConfig struct {
}
type WebConfig struct {
Listen string `yaml:"listen"`
ListenTLS string `yaml:"listen_tls"`
LoadLocalFiles bool `yaml:"load_local_files" env:"AUTHENTIK_WEB_LOAD_LOCAL_FILES"`
Listen string `yaml:"listen"`
ListenTLS string `yaml:"listen_tls"`
LoadLocalFiles bool `yaml:"load_local_files" env:"AUTHENTIK_WEB_LOAD_LOCAL_FILES"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"AUTHENTIK_WEB__DISABLE_EMBEDDED_OUTPOST"`
}
type PathsConfig struct {

View File

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

View File

@ -47,7 +47,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
config.Host = akURL.Host
config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{
Transport: NewTracingTransport(GetTLSTransport()),
Transport: NewTracingTransport(context.TODO(), GetTLSTransport()),
}
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
@ -107,8 +107,24 @@ func (a *APIController) Start() error {
return nil
}
func (a *APIController) OnRefresh() error {
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
outposts, _, err := a.Client.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch outpost configuration")
return err
}
outpost := outposts.Results[0]
doGlobalSetup(outpost.Config)
log.WithField("name", outpost.Name).Debug("Fetched outpost configuration")
return a.Server.Refresh()
}
func (a *APIController) StartBackgorundTasks() error {
err := a.Server.Refresh()
err := a.OnRefresh()
if err != nil {
return errors.Wrap(err, "failed to run initial refresh")
}

View File

@ -82,7 +82,7 @@ func (ac *APIController) startWSHandler() {
if wsMsg.Instruction == WebsocketInstructionTriggerUpdate {
time.Sleep(ac.reloadOffset)
logger.Debug("Got update trigger...")
err := ac.Server.Refresh()
err := ac.OnRefresh()
if err != nil {
logger.WithError(err).Debug("Failed to update")
}
@ -118,7 +118,7 @@ func (ac *APIController) startIntervalUpdater() {
logger := ac.logger.WithField("loop", "interval-updater")
ticker := time.NewTicker(5 * time.Minute)
for ; true; <-ticker.C {
err := ac.Server.Refresh()
err := ac.OnRefresh()
if err != nil {
logger.WithError(err).Debug("Failed to update")
}

View File

@ -1,6 +1,7 @@
package ak
import (
"context"
"net/http"
"github.com/getsentry/sentry-go"
@ -8,14 +9,15 @@ import (
type tracingTransport struct {
inner http.RoundTripper
ctx context.Context
}
func NewTracingTransport(inner http.RoundTripper) *tracingTransport {
return &tracingTransport{inner}
func NewTracingTransport(ctx context.Context, inner http.RoundTripper) *tracingTransport {
return &tracingTransport{inner, ctx}
}
func (tt *tracingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
span := sentry.StartSpan(r.Context(), "authentik.go.http_request")
span := sentry.StartSpan(tt.ctx, "authentik.go.http_request")
span.SetTag("url", r.URL.String())
span.SetTag("method", r.Method)
defer span.Finish()

View File

@ -16,6 +16,7 @@ import (
"goauthentik.io/api"
"goauthentik.io/internal/constants"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/utils"
)
type StageComponent string
@ -61,8 +62,10 @@ func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Config
config.UserAgent = constants.OutpostUserAgent()
config.HTTPClient = &http.Client{
Jar: jar,
Transport: ak.NewTracingTransport(ak.GetTLSTransport()),
Transport: ak.NewTracingTransport(ctx, ak.GetTLSTransport()),
}
token := strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1]
config.AddDefaultHeader(HeaderAuthentikOutpostToken, token)
apiClient := api.NewAPIClient(config)
return &FlowExecutor{
Params: url.Values{},
@ -71,7 +74,7 @@ func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Config
api: apiClient,
flowSlug: flowSlug,
log: l,
token: strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1],
token: token,
sp: rsp,
}
}
@ -87,13 +90,7 @@ type ChallengeInt interface {
}
func (fe *FlowExecutor) DelegateClientIP(a net.Addr) {
host, _, err := net.SplitHostPort(a.String())
if err != nil {
fe.log.WithError(err).Warning("Failed to get remote IP")
return
}
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikRemoteIP, host)
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikOutpostToken, fe.token)
fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikRemoteIP, utils.GetIP(a))
}
func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) {
@ -152,7 +149,9 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
responseReq := fe.api.FlowsApi.FlowsExecutorSolve(scsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
switch ch.GetComponent() {
case string(StageIdentification):
responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))))
r := api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))
r.SetPassword(fe.getAnswer(StagePassword))
responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(r))
case string(StagePassword):
responseReq = responseReq.FlowChallengeResponseRequest(api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewPasswordChallengeResponseRequest(fe.getAnswer(StagePassword))))
case string(StageAuthenticatorValidate):

View File

@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/utils"
)
type BindRequest struct {
@ -33,7 +34,7 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
BindDN: bindDN,
BindPW: bindPW,
conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()),
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", utils.GetIP(conn.RemoteAddr())),
id: rid,
ctx: span.Context(),
}

View File

@ -0,0 +1,32 @@
package ldap
import (
"net"
"time"
)
func (ls *LDAPServer) Close(boundDN string, conn net.Conn) error {
for _, p := range ls.providers {
p.delayDeleteUserInfo(boundDN)
}
return nil
}
func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
ticker := time.NewTicker(30 * time.Second)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
pi.boundUsersMutex.Lock()
delete(pi.boundUsers, dn)
pi.boundUsersMutex.Unlock()
close(quit)
case <-quit:
ticker.Stop()
return
}
}
}()
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"strings"
"time"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3"
@ -12,6 +11,7 @@ import (
log "github.com/sirupsen/logrus"
"goauthentik.io/api"
"goauthentik.io/internal/outpost"
"goauthentik.io/internal/utils"
)
const ContextUserKey = "ak_user"
@ -37,7 +37,7 @@ func (pi *ProviderInstance) getUsername(dn string) (string, error) {
func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPResultCode, error) {
fe := outpost.NewFlowExecutor(req.ctx, pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
"bindDN": req.BindDN,
"client": req.conn.RemoteAddr().String(),
"client": utils.GetIP(req.conn.RemoteAddr()),
"requestId": req.id,
})
fe.DelegateClientIP(req.conn.RemoteAddr())
@ -83,7 +83,6 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
}
uisp.Finish()
defer pi.boundUsersMutex.Unlock()
pi.delayDeleteUserInfo(username)
return ldap.LDAPResultSuccess, nil
}
@ -100,25 +99,6 @@ func (pi *ProviderInstance) SearchAccessCheck(user api.UserSelf) *string {
return nil
}
func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
ticker := time.NewTicker(30 * time.Second)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
pi.boundUsersMutex.Lock()
delete(pi.boundUsers, dn)
pi.boundUsersMutex.Unlock()
close(quit)
case <-quit:
ticker.Stop()
return
}
}
}()
}
func (pi *ProviderInstance) TimerFlowCacheExpiry() {
fe := outpost.NewFlowExecutor(context.Background(), pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{})
fe.Params.Add("goauthentik.io/outpost/ldap", "true")

View File

@ -83,5 +83,6 @@ func NewServer(ac *ak.APIController) *LDAPServer {
ls.defaultCert = &defaultCert
s.BindFunc("", ls)
s.SearchFunc("", ls)
s.CloseFunc("", ls)
return ls
}

View File

@ -11,6 +11,7 @@ import (
"github.com/google/uuid"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/utils"
)
type SearchRequest struct {
@ -35,7 +36,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
SearchRequest: searchReq,
BindDN: bindDN,
conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
id: rid,
ctx: span.Context(),
}

View File

@ -25,6 +25,7 @@ type providerBundle struct {
Host string
endSessionUrl string
Mode *api.ProxyMode
cert *tls.Certificate
@ -38,8 +39,8 @@ func intToPointer(i int) *int {
func (pb *providerBundle) replaceLocal(url string) string {
if strings.HasPrefix(url, "http://localhost:8000") {
authentikHost, c := pb.s.ak.Outpost.Config["authentik_host"]
if !c {
pb.log.Warning("Outpost has localhost API Connection but no authentik_host is configured.")
if !c || authentikHost == "" {
pb.log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.")
return url
}
f := strings.ReplaceAll(url, "http://localhost:8000", authentikHost.(string))
@ -49,6 +50,10 @@ func (pb *providerBundle) replaceLocal(url string) string {
}
func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.Options {
// We need to save the mode in the bundle
// Since for the embedded outpost we only switch for fully proxy providers
pb.Mode = provider.Mode
externalHost, err := url.Parse(provider.ExternalHost)
if err != nil {
log.WithError(err).Warning("Failed to parse URL, skipping provider")

View File

@ -22,6 +22,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/providers"
"goauthentik.io/api"
"goauthentik.io/internal/utils/web"
staticWeb "goauthentik.io/web"
log "github.com/sirupsen/logrus"
)
@ -121,7 +122,7 @@ func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig, c *ht
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
}
logger.Printf("proxy instance configured for Client ID: %s", opts.ClientID)
logger.WithField("auth_url", opts.GetProvider().Data().LoginURL.String()).WithField("client_id", opts.ClientID).Info("proxy instance configured")
sessionChain := buildSessionChain(opts, sessionStore)
@ -255,11 +256,18 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
p.AuthenticateOnly(rw, req)
case path == p.UserInfoPath:
p.UserInfo(rw, req)
case strings.HasPrefix(path, fmt.Sprintf("%s/static", p.ProxyPrefix)):
p.ServeStatic(rw, req)
default:
p.Proxy(rw, req)
}
}
func (p *OAuthProxy) ServeStatic(rw http.ResponseWriter, req *http.Request) {
staticFs := http.FileServer(http.FS(staticWeb.StaticDist))
http.StripPrefix(fmt.Sprintf("%s/static", p.ProxyPrefix), staticFs).ServeHTTP(rw, req)
}
//UserInfo endpoint outputs session email and preferred username in JSON format
func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {

View File

@ -4,25 +4,11 @@ import (
"html/template"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/proxy/templates"
)
func getTemplates() *template.Template {
t, err := template.New("foo").Parse(`{{define "error.html"}}
<!DOCTYPE html>
<html lang="en" charset="utf-8">
<head>
<title>{{.Title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<style>* { font-family: sans-serif; }</style>
</head>
<body>
<h2>{{.Title}}</h2>
<p>{{.Message}}</p>
<hr>
<p><a href="{{.ProxyPrefix}}/sign_in">Sign In</a></p>
<p>Powered by <a href="https://goauthentik.io">authentik</a></p>
</body>
</html>{{end}}`)
t, err := template.New("foo").Parse(templates.ErrorTemplate)
if err != nil {
log.Fatalf("failed parsing template %s", err)
}

View File

@ -0,0 +1,65 @@
{{define "error.html"}}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/akprox/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/akprox/static/dist/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="/akprox/static/dist/authentik.css">
<style>
.pf-c-background-image::before {
--ak-flow-background: url("/akprox/static/dist/assets/images/flow_background.jpg");
}
</style>
</head>
<body>
<div class="pf-c-background-image">
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
<filter id="image_overlay">
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
<feFuncA type="table" tableValues="0 1"></feFuncA>
</feComponentTransfer>
</filter>
</svg>
</div>
<div class="pf-c-login">
<div class="ak-login-container">
<header class="pf-c-login__header">
<div class="pf-c-brand ak-brand">
<img src="/akprox/static/dist/assets/icons/icon_left_brand.svg" alt="authentik icon" />
</div>
</header>
<main class="pf-c-login__main">
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .Title }}
</h1>
</header>
<div class="pf-c-login__main-body">
{{ .Message }}
</div>
<div class="pf-c-login__main-body">
<a href="/" class="pf-c-button pf-m-primary pf-m-block">Go to home</a>
</div>
</main>
<footer class="pf-c-login__footer">
<p></p>
<ul class="pf-c-list pf-m-inline">
<li>
<a href="https://goauthentik.io">
Powered by authentik
</a>
</li>
</ul>
</footer>
</div>
</div>
</body>
</html>
{{end}}

View File

@ -0,0 +1,6 @@
package templates
import _ "embed"
//go:embed error.html
var ErrorTemplate string

13
internal/utils/net.go Normal file
View File

@ -0,0 +1,13 @@
package utils
import "net"
func GetIP(addr net.Addr) string {
switch addr := addr.(type) {
case *net.UDPAddr:
return addr.IP.String()
case *net.TCPAddr:
return addr.IP.String()
}
return ""
}

View File

@ -9,19 +9,21 @@ import (
"goauthentik.io/internal/utils/web"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
span := sentry.StartSpan(r.Context(), "authentik.go.request")
before := time.Now()
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
after := time.Now()
log.WithFields(log.Fields{
"remote": r.RemoteAddr,
"method": r.Method,
"took": after.Sub(before),
"host": web.GetHost(r),
}).Info(r.RequestURI)
span.Finish()
})
func loggingMiddleware(l *log.Entry) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
span := sentry.StartSpan(r.Context(), "authentik.go.request")
before := time.Now()
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
after := time.Now()
l.WithFields(log.Fields{
"remote": r.RemoteAddr,
"method": r.Method,
"took": after.Sub(before),
"host": web.GetHost(r),
}).Info(r.RequestURI)
span.Finish()
})
}
}

View File

@ -30,6 +30,7 @@ type WebServer struct {
}
func NewWebServer() *WebServer {
l := log.WithField("logger", "authentik.g.web")
mainHandler := mux.NewRouter()
if config.G.ErrorReporting.Enabled {
mainHandler.Use(recoveryMiddleware())
@ -37,14 +38,14 @@ func NewWebServer() *WebServer {
mainHandler.Use(handlers.ProxyHeaders)
mainHandler.Use(handlers.CompressHandler)
logginRouter := mainHandler.NewRoute().Subrouter()
logginRouter.Use(loggingMiddleware)
logginRouter.Use(loggingMiddleware(l))
ws := &WebServer{
LegacyProxy: true,
m: mainHandler,
lh: logginRouter,
log: log.WithField("logger", "authentik.g.web"),
log: l,
}
ws.configureStatic()
ws.configureProxy()

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