Compare commits

..

465 Commits

Author SHA1 Message Date
eaad564e23 release: 2022.1.5 2022-02-09 22:31:26 +01:00
511a94975b website/docs: add 2022.1.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:31:14 +01:00
015810a2fd internal: fix CSRF error caused by Host header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:53 +01:00
e70e6b84c2 internal: trace headers and url for backend requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:50 +01:00
d0b9c9a26f internal: remove uvicorn server header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:46 +01:00
3e403fa348 internal: improve error handling for internal reverse proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:41 +01:00
48f4a971ef internal: don't attempt to lookup SNI Certificate if no SNI is sent
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:39 +01:00
6314be14ad core: allow formatting strings to be used for applications' launch URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:29 +01:00
1a072c6c39 web/admin: fix mismatched icons in overview and lists
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:26 +01:00
ef2eed0bdf outposts: fix compare_ports to support both service and container ports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:20 +01:00
91227b1e96 outposts: fix service reconciler re-creating services
closes #2095

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:16 +01:00
67d68629da providers/proxy: fix Host/:Authority not being modified
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:08 +01:00
e875db8f66 stages/authenticator_validate: handle non-existent device_challenges
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:02 +01:00
055a76393d outposts: remove node_port on V1ServicePort checks to prevent service creation loops
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2095
2022-02-09 22:21:58 +01:00
0754821628 providers/proxy: improve error handling for invalid backend_override
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:55 +01:00
fca88d9896 sources/ldap: log entire exception
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:48 +01:00
dfe0404c51 sources/saml: fix server error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:24 +01:00
fa61696b46 sources/saml: fix incorrect ProtocolBinding being sent
closes #2213

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:15 +01:00
e5773738f4 outposts: fix channel not always having a logger attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:12 +01:00
cac8539d79 providers/proxy: fix nil error in claims
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:08 +01:00
cf600f6f26 build(deps): bump uvicorn from 0.17.1 to 0.17.3 (#2229)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.17.1 to 0.17.3.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.17.1...0.17.3)

---
updated-dependencies:
- dependency-name: uvicorn
  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>
2022-02-09 17:56:53 +01:00
49dfb4756e release: 2022.1.4 2022-02-01 20:12:55 +01:00
814758e2aa website/docs: prepare 2022.1.4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-01 19:27:25 +01:00
5c42dac5e2 web/user: include locale code in locale selection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-01 19:19:37 +01:00
88603fa4f7 providers/proxy: set traefik labels using object_naming_template instead of UUID 2022-02-01 17:13:27 +00:00
0232c4e162 lifecycle: send analytics in gunicorn config to decrease outgoing requests when workers get restarted
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-01 15:01:43 +01:00
11753c1fe1 build(deps): bump django from 4.0.1 to 4.0.2 (#2204)
Bumps [django](https://github.com/django/django) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.0.1...4.0.2)

---
updated-dependencies:
- dependency-name: django
  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>
2022-02-01 10:59:04 +01:00
f5cc6c67ec providers/proxy: fix routing for external_host when using forward_auth_domain
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2180
2022-02-01 10:14:46 +01:00
8b8ed3527a build(deps): bump @typescript-eslint/parser in /web (#2200) 2022-02-01 09:11:41 +01:00
1aa0274e7c build(deps): bump @typescript-eslint/eslint-plugin in /web (#2201) 2022-02-01 09:09:34 +01:00
ecd33ca0c1 build(deps): bump github.com/go-openapi/runtime from 0.21.1 to 0.22.0 (#2202) 2022-02-01 09:09:18 +01:00
e93be0de9a sources/ldap: add list_flatten function to property mappings, enable on managed LDAP mappings
closes #2199

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-31 23:07:32 +01:00
a5adc4f8ed core: fix view_token permission not being assigned on token creation for non-admin user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-31 20:00:30 +01:00
a6baed9753 web/flows: fix width on flow container 2022-01-31 14:11:25 +00:00
ceaf832e63 root: remove boto integration in sentry to ease backup removal 2022-01-31 13:47:18 +00:00
a6b0b14685 Translate /web/src/locales/en.po in pl (#2197)
translation completed for the source file '/web/src/locales/en.po'
on the 'pl' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-31 14:17:26 +01:00
f679250edd lifecycle: remove gunicorn reload option
should help with #2159
2022-01-31 12:06:08 +00:00
acc4de2235 web: add pl locale 2022-01-31 11:50:05 +00:00
56a8276dbf website/integrations: update active directory docs (#2177) 2022-01-31 12:11:01 +01:00
6dfe6edbef website/integrations: add zulip (#2106)
* add zulip to sidebar links

* added Zulip chat integration documentation

* fix markdown typo

* add note about using Post for saml binding

* added missing ACS info and cleaned up

format matches other integration documents
2022-01-31 12:10:30 +01:00
6af4bd0d9a build(deps): bump construct-style-sheets-polyfill in /web (#2189)
Bumps [construct-style-sheets-polyfill](https://github.com/calebdwilliams/construct-style-sheets) from 3.0.5 to 3.1.0.
- [Release notes](https://github.com/calebdwilliams/construct-style-sheets/releases)
- [Changelog](https://github.com/calebdwilliams/construct-style-sheets/blob/main/CHANGELOG.md)
- [Commits](https://github.com/calebdwilliams/construct-style-sheets/commits)

---
updated-dependencies:
- dependency-name: construct-style-sheets-polyfill
  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>
2022-01-31 12:09:13 +01:00
7ee7f6bd6a Translate /web/src/locales/en.po in pl (#2196)
translation completed for the source file '/web/src/locales/en.po'
on the 'pl' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-31 10:00:37 +01:00
f8b8334010 build(deps): bump @patternfly/patternfly from 4.164.2 to 4.171.1 in /web (#2192) 2022-01-31 09:05:17 +01:00
d4b65dc4b4 build(deps): bump @sentry/browser from 6.17.2 to 6.17.3 in /web (#2191) 2022-01-31 09:04:40 +01:00
e4bbd3b1c0 build(deps): bump eslint from 8.7.0 to 8.8.0 in /web (#2190) 2022-01-31 09:03:47 +01:00
87de5e625d build(deps): bump @sentry/tracing from 6.17.2 to 6.17.3 in /web (#2193) 2022-01-31 09:03:32 +01:00
efbe51673e build(deps): bump pycryptodome from 3.13.0 to 3.14.0 (#2194) 2022-01-31 09:03:10 +01:00
a95bea53ea build(deps): bump github.com/prometheus/client_golang (#2195) 2022-01-31 09:02:56 +01:00
6021fc0f52 providers/proxy: fix backend override persisting for other users
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-30 22:29:34 +01:00
1415b68ff4 web: add es locale
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-30 21:43:55 +01:00
be6853ac52 Translate /web/src/locales/en.po in es (#2188)
translation completed for the source file '/web/src/locales/en.po'
on the 'es' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-30 21:38:30 +01:00
7fd6be5abb providers/proxy: add backend_override
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-30 21:35:08 +01:00
91d6f572a5 scripts: cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-30 21:34:37 +01:00
016a9ce34e build(deps): bump boto3 from 1.20.45 to 1.20.46 (#2187) 2022-01-30 00:52:25 +01:00
8adb95af7f build(deps): bump uvicorn from 0.17.0.post1 to 0.17.1 (#2186) 2022-01-30 00:52:08 +01:00
1dc54775d8 build(deps): bump requests-oauthlib from 1.3.0 to 1.3.1 (#2185) 2022-01-30 00:51:59 +01:00
370ef716b5 build(deps-dev): bump black from 21.12b0 to 22.1.0 (#2184) 2022-01-30 00:51:49 +01:00
16e56ad9ca website/docs: add rough documentation style guide
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-29 23:52:03 +01:00
b5b5a9eed3 web/admin: only check first half of locale when detecting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2178
2022-01-28 12:35:37 +01:00
8b22e7bcc3 core: compile backend translations (#2179) 2022-01-28 11:09:29 +01:00
d48b5b9511 Translate /locale/en/LC_MESSAGES/django.po in es (#2175)
translation completed for the source file '/locale/en/LC_MESSAGES/django.po'
on the 'es' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-28 09:55:56 +01:00
0eccaa3f1e build(deps): bump boto3 from 1.20.44 to 1.20.45 (#2176)
Bumps [boto3](https://github.com/boto/boto3) from 1.20.44 to 1.20.45.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.20.44...1.20.45)

---
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>
2022-01-28 09:55:16 +01:00
67d550a80d providers/proxy: don't include hostname and scheme in redirect when we only got a path and not a full URL
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-27 18:23:08 +01:00
ebb5711c32 providers/proxy: add support for X-Original-URI in nginx, better handle missing headers and report errors to authentik
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-27 18:14:02 +01:00
79ec872232 build(deps): bump @docusaurus/plugin-client-redirects in /website (#2173)
Bumps [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) from 2.0.0-beta.14 to 2.0.0-beta.15.
- [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.15/packages/docusaurus-plugin-client-redirects)

---
updated-dependencies:
- dependency-name: "@docusaurus/plugin-client-redirects"
  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>
2022-01-27 10:14:14 +01:00
4284e14ff7 build(deps): bump @docusaurus/preset-classic in /website (#2172)
Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 2.0.0-beta.14 to 2.0.0-beta.15.
- [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.15/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>
2022-01-27 10:12:06 +01:00
92a09779d0 build(deps): bump boto3 from 1.20.43 to 1.20.44 (#2174) 2022-01-27 09:28:02 +01:00
14c621631d web: Update Web API Client version (#2170)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-26 23:30:56 +01:00
c55f503b9b release: 2022.1.3 2022-01-26 22:15:28 +01:00
a908cad976 website/docs: add release notes for 2022.1.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-26 21:41:15 +01:00
c2586557d8 root: fix redis passwords not being encoded correctly
closes #2130

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-26 20:45:45 +01:00
01c80a82e2 web/admin: fix SMS Stage form not working
closes #2127

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-26 20:39:38 +01:00
0d47654651 root: add max-requests for gunicorn and max tasks for celery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-26 10:04:58 +01:00
1183095833 build(deps): bump @sentry/tracing from 6.17.1 to 6.17.2 in /web (#2162) 2022-01-26 09:35:10 +01:00
c281b11bdc build(deps): bump lit from 2.1.1 to 2.1.2 in /web (#2161) 2022-01-26 09:22:05 +01:00
61fe45a58c build(deps): bump @sentry/browser from 6.17.1 to 6.17.2 in /web (#2163) 2022-01-26 09:21:52 +01:00
d43aab479c build(deps): bump rollup from 2.66.0 to 2.66.1 in /web (#2164) 2022-01-26 09:21:43 +01:00
7f8383427a build(deps): bump sentry-sdk from 1.5.3 to 1.5.4 (#2165) 2022-01-26 09:21:24 +01:00
a06d6cf33d build(deps-dev): bump bandit from 1.7.1 to 1.7.2 (#2166) 2022-01-26 09:21:09 +01:00
5b7cb205c9 build(deps): bump boto3 from 1.20.42 to 1.20.43 (#2167) 2022-01-26 09:20:50 +01:00
293a932d20 build(deps-dev): bump coverage from 6.2 to 6.3 (#2168) 2022-01-26 09:20:34 +01:00
fff901ff03 rootL Fix goauthentik.io URL in Readme (#2158) 2022-01-25 20:36:44 +01:00
f47c936295 internal: add optional debug server listening on 9900
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-25 17:18:53 +01:00
88d5aec618 web/admin: fix links which look like labels
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-25 16:13:30 +01:00
96ae68cf09 internal: make error message less confusing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-25 15:45:21 +01:00
63b3434b6f website/docs: improve nginx examples
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-25 14:25:21 +01:00
947ecec02b outposts/ldap: Fix more case sensitivity issues. (#2144) 2022-01-25 11:27:27 +01:00
1c2b452406 outposts/proxy: fix potential empty redirect, add tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2141
2022-01-25 10:57:53 +01:00
47777529ac build(deps): bump @formatjs/intl-listformat from 6.5.0 to 6.5.1 in /web (#2154) 2022-01-25 09:50:29 +01:00
949095c376 build(deps): bump @lingui/macro from 3.13.1 to 3.13.2 in /web (#2152) 2022-01-25 09:49:59 +01:00
4b112c2799 build(deps): bump @sentry/browser from 6.16.1 to 6.17.1 in /web (#2146) 2022-01-25 09:49:48 +01:00
291a2516b1 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2149) 2022-01-25 09:49:29 +01:00
4dcfd021e2 build(deps): bump @lingui/detect-locale from 3.13.1 to 3.13.2 in /web (#2147) 2022-01-25 09:49:13 +01:00
ca50848db3 build(deps): bump boto3 from 1.20.41 to 1.20.42 (#2156) 2022-01-25 09:49:01 +01:00
0bb3e3c558 build(deps): bump @lingui/cli from 3.13.1 to 3.13.2 in /web (#2148) 2022-01-25 09:48:50 +01:00
e4b25809ab build(deps): bump @typescript-eslint/parser in /web (#2150) 2022-01-25 09:48:03 +01:00
7bf932f8e2 build(deps): bump @sentry/tracing from 6.16.1 to 6.17.1 in /web (#2151) 2022-01-25 09:47:52 +01:00
99d04528b0 build(deps): bump country-flag-icons from 1.4.19 to 1.4.20 in /web (#2153) 2022-01-25 09:47:43 +01:00
e48d172036 build(deps): bump @lingui/core from 3.13.1 to 3.13.2 in /web (#2155) 2022-01-25 09:47:34 +01:00
c2388137a8 build(deps): bump uvicorn from 0.17.0 to 0.17.0.post1 (#2157) 2022-01-25 09:47:05 +01:00
650e2cbc38 internal: remove duplicate log messages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 22:25:35 +01:00
b32800ea71 outposts/proxy: trace full headers to debug
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 22:08:31 +01:00
e1c0c0b20c internal: don't override server header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 22:05:11 +01:00
fe39e39dcd lifecycle: make secret_key warning more prominent
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2131
2022-01-24 21:52:16 +01:00
883f213b03 lifecycle: wait for db in worker
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 21:51:57 +01:00
538996f617 web: Update Web API Client version (#2143)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-24 21:46:39 +01:00
2f4c92deb9 Merge branch 'version-2022.1' 2022-01-24 21:42:12 +01:00
ef335ec083 outposts/proxy: add more test cases for domain-level auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 21:41:15 +01:00
07b09df3fe internal: add more outpost tests, add support for X-Original-URL
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 20:50:13 +01:00
e70e031a1f internal: start adding tests to outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 20:12:25 +01:00
c7ba183dc0 providers/proxy: fix traefik label
closes #2128

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 17:45:09 +01:00
3ed23a37ea website/docs: add 2022.1.2 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 11:34:13 +01:00
3d724db0e3 release: 2022.1.2 2022-01-24 11:28:00 +01:00
2997542114 lib: disable backup by default, add note to configuration 2022-01-24 10:00:15 +00:00
84b18fff96 ci: cache-v2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 09:37:04 +01:00
1dce408c72 internal/proxyv2: only allow access to /akprox in nginx mode when forward url could be extracted
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-24 09:30:33 +01:00
e5ff47bf14 build(deps): bump @lingui/cli from 3.13.0 to 3.13.1 in /web (#2133) 2022-01-24 08:49:03 +01:00
b53bf331c3 build(deps): bump @lingui/macro from 3.13.0 to 3.13.1 in /web (#2135) 2022-01-24 08:48:51 +01:00
90e9a8b34c build(deps): bump rollup from 2.64.0 to 2.66.0 in /web (#2139) 2022-01-24 08:48:26 +01:00
845f842783 build(deps): bump @lingui/core from 3.13.0 to 3.13.1 in /web (#2136) 2022-01-24 08:48:17 +01:00
7397849c60 build(deps): bump rapidoc from 9.1.3 to 9.1.4 in /website (#2132) 2022-01-24 08:47:45 +01:00
6dd46b5fc5 build(deps): bump @babel/core from 7.16.10 to 7.16.12 in /web (#2134) 2022-01-24 08:47:35 +01:00
89ca79ed10 build(deps): bump @lingui/detect-locale from 3.13.0 to 3.13.1 in /web (#2137) 2022-01-24 08:47:15 +01:00
713bef895c build(deps): bump rapidoc from 9.1.3 to 9.1.4 in /web (#2138) 2022-01-24 08:46:37 +01:00
925115e9ce build(deps): bump github.com/go-openapi/runtime from 0.21.0 to 0.21.1 (#2140) 2022-01-24 08:46:17 +01:00
42f5cf8c93 outposts: allow custom label for docker containers
closes #2128

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-23 21:55:58 +01:00
82cc1d536a providers/proxy: add PathPrefix to auto-traefik labels
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2128
2022-01-23 21:55:46 +01:00
08af2fd46b website/docs: deprecate inbuilt backup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-23 21:51:22 +01:00
70e3b27a4d root: upgrade python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-23 21:27:16 +01:00
6a411d7960 policies/hibp: ensure password is encodable
closes AUTHENTIK-1SA

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-23 21:23:24 +01:00
33567b56d7 lifecycle: replace lowercase, deprecated prometheus_multiproc_dir
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-23 21:21:06 +01:00
0c1954aeb7 web: Update Web API Client version (#2126)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-22 19:06:20 +01:00
f4a6c70e98 release: 2022.1.1 2022-01-22 18:28:40 +01:00
5f198e7fe4 website/docs: update 2022.1
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-22 12:29:20 +01:00
d172d32817 ci: bump golangci
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 23:22:59 +01:00
af3fb5c2cd internal: use math.MaxInt for compatibility
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1819
2022-01-21 23:11:17 +01:00
885efb526e web/admin: also set embedded outpost host when it doesn't include scheme
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 13:51:34 +01:00
3bfb8b2cb2 outposts/proxyv2: allow access to /akprox urls in forward auth mode to make routing in nginx/traefik easier
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 13:43:16 +01:00
9fc5ff4b77 outposts/proxyv2: fix JWKS url pointing to localhost on embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 13:29:51 +01:00
dd8b579dd6 lib: ignore paramiko logger
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 10:46:33 +01:00
e12cbd8711 website/docs: add 2022.1
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-21 09:47:49 +01:00
62d35f8f8c build(deps): bump codemirror from 5.65.0 to 5.65.1 in /web (#2122) 2022-01-21 09:06:27 +01:00
49be504c13 build(deps): bump @babel/preset-env from 7.16.10 to 7.16.11 in /web (#2123) 2022-01-21 09:06:04 +01:00
edad55e51d build(deps): bump typescript from 4.5.4 to 4.5.5 in /web (#2124) 2022-01-21 09:05:51 +01:00
38086fa8bb build(deps): bump boto3 from 1.20.39 to 1.20.40 (#2125) 2022-01-21 09:05:32 +01:00
c4f9a3e9a7 build(deps): bump @babel/preset-env from 7.16.8 to 7.16.10 in /web (#2114) 2022-01-20 08:45:49 +01:00
930df791bd build(deps): bump python (#2113) 2022-01-20 08:45:28 +01:00
9a6086634c build(deps): bump boto3 from 1.20.38 to 1.20.39 (#2117) 2022-01-20 08:45:03 +01:00
b68e65355a build(deps): bump @babel/core from 7.16.7 to 7.16.10 in /web (#2115) 2022-01-20 08:44:47 +01:00
72d33a91dd build(deps): bump @babel/plugin-transform-runtime in /web (#2116) 2022-01-20 08:44:28 +01:00
7067e3d69a build(deps): bump github.com/prometheus/client_golang (#2118) 2022-01-20 08:44:01 +01:00
4db370d24e website/docs: add flow inspector docs
closes #2105

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-19 19:31:21 +01:00
41e7b9b73f outposts/proxyv2: fix before-redirect url not being saved in proxy mode
closes #2109

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-19 19:16:30 +01:00
7f47f93e4e internal: cleanup log messages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-19 19:01:24 +01:00
89abd44b76 lifecycle: add early check for missing/invalid secret key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-19 09:53:53 +01:00
14c7d8c4f4 internal: route traffic to proxy providers based on cookie domain when multiple domain-level providers exist
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2079
2022-01-18 23:19:43 +01:00
525976a81b root: upgrade python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-18 21:35:07 +01:00
64a2126ea4 website/docs: fix typo in port mapping in manifest (#2103)
Otherwise it causes:

```
error: error validating "outpost.yaml": error validating data: ValidationError(Service.spec.ports[1].port): invalid type for io.k8s.api.core.v1.ServicePort.port: got "string", expected "integer"; if you choose to ignore these errors, turn validation off with --validate=false
```
2022-01-18 19:57:55 +01:00
994c5882ab root: fix error if secret_key is purely numerical
closes #2099

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-18 09:17:33 +01:00
ad64d51e85 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2100)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.9.1 to 5.10.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.10.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
2022-01-18 09:16:54 +01:00
a184a7518a build(deps): bump @typescript-eslint/parser from 5.9.1 to 5.10.0 in /web (#2101)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.9.1 to 5.10.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.10.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  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>
2022-01-18 09:14:58 +01:00
943fd80920 web: ignore additional error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-17 22:05:23 +01:00
01bb18b8c4 root: allow customisation of ports in compose without override
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-17 14:48:02 +01:00
94baaaa5a5 build(deps): bump eslint from 8.6.0 to 8.7.0 in /web (#2096) 2022-01-17 09:19:59 +01:00
40b164ce94 build(deps): bump rollup from 2.63.0 to 2.64.0 in /web (#2097) 2022-01-17 09:18:30 +01:00
1d7c7801e7 website/docs: prepare 2022.1 release
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-16 18:22:25 +01:00
0db0a12ef3 root: rename csrf header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-16 16:17:44 +01:00
8008aba450 web: directly read csrf token before injecting into request
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-16 16:10:55 +01:00
eaeab27004 lib: add support for custom env
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-16 14:56:02 +01:00
111fbf119b *: refactor prometheus gauges to directly updating metrics view
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-16 13:57:07 +01:00
300ad88447 web: add polyfill for Intl.ListFormat
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-15 14:56:18 +01:00
92cc0c9c64 root: decrease to 10 backup history
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-14 19:59:50 +01:00
18ff803370 outposts: trigger service update on k8s when selector doesnt match
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-14 11:42:57 +01:00
819af78e2b internal: make internal go version match python version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-14 10:45:37 +01:00
6338785ce1 outposts: change label app.kubernetes.io/name to include outpost type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-14 10:34:54 +01:00
973e151dff outposts: add Additional version labels to managed k8s deployments
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-13 17:48:01 +01:00
fae6d83f27 *: simplify extracting current version info
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-13 17:47:31 +01:00
ed84fe0b8d root: set samesite for csrf cookie
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 23:14:14 +01:00
1ee603403e root: upgrade python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 23:02:39 +01:00
7db7b7cc4d stages/authenticator_validate: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 23:00:28 +01:00
68a98cd86c web: Update Web API Client version (#2091)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-12 22:59:59 +01:00
e758db5727 stages/authenticator_webauthn: make more WebAuthn options configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 22:57:49 +01:00
4d7d700afa providers/oauth2: change default redirect uri behaviour; set first used url when blank and use star for wildcard
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 22:44:57 +01:00
f9a5add01d root: include build in analytics
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 22:18:52 +01:00
2986b56389 root: fix backups running every minute instead of once
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-12 22:09:44 +01:00
58f79b525d web/admin: fix invalid build due to wrong import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-11 11:40:59 +01:00
0a1c0dae05 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2085) 2022-01-11 08:59:13 +01:00
e18ef8dab6 build(deps): bump @babel/preset-env from 7.16.7 to 7.16.8 in /web (#2087) 2022-01-11 08:58:45 +01:00
3cacc59bec build(deps): bump @typescript-eslint/parser from 5.9.0 to 5.9.1 in /web (#2086) 2022-01-11 08:57:51 +01:00
4eea46d399 build(deps): bump @babel/plugin-transform-runtime in /web (#2088) 2022-01-11 08:57:28 +01:00
11e25617bd crypto: fully parse certificate on validation in serializer to prevent invalid certificates from being saved
closes #2082

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 20:36:50 +01:00
4817126811 website/integrations: fix synapse docs based on upstream docs
closes #2080

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 20:19:56 +01:00
0181361efa website/integrations: use Signing Key instead of RSA Key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 20:18:05 +01:00
8ff8e1d5f7 web/admin: fix missing configure flow setting on webuahtn setup stage form
closes #2084

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 20:15:34 +01:00
19d5902a92 flows: handle error if flow title contains invalid format string
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 19:49:27 +01:00
71dffb21a9 outposts: improve error handling for outpost service connection state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 19:44:13 +01:00
bd283c506d web/flows: remove node directly instead of using removeChild()
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 19:37:51 +01:00
ef564e5f1a web: fix double plural in label
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 18:58:56 +01:00
2543224c7c core: dont return 404 when trying to view key of expired token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-10 17:53:09 +01:00
077eee9310 Revert "build(deps): bump goauthentik.io/api from 0.2021125.1 to 1.2021.9 (#2083)"
This reverts commit d894eeaa67.
2022-01-10 10:03:48 +01:00
d894eeaa67 build(deps): bump goauthentik.io/api from 0.2021125.1 to 1.2021.9 (#2083)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.2021125.1 to 1.2021.9.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-10 09:39:30 +01:00
452bfb39bf Revert "web/elements: re-enable codemirror line numbers (fixed on firefox)"
This reverts commit 4c166dcf52.

closes #2081

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-09 22:24:32 +01:00
6b6702521f api: don't return error reporting enabled when debug is enabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-07 21:53:22 +01:00
c07b8d95d0 outposts/proxy: remove deprecated headers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-07 17:01:23 +01:00
bf347730b3 outposts/ldap: remove deprecated fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-07 09:52:19 +01:00
ececfc3a30 internal: fix comment formatting for TODOs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-07 09:51:41 +01:00
b76546de0c Translate /web/src/locales/en.po in tr (#2074) 2022-01-07 09:16:02 +01:00
424d490a60 build(deps): bump golang from 1.17.5-bullseye to 1.17.6-bullseye (#2075) 2022-01-07 09:14:44 +01:00
127dd85214 build(deps): bump lit from 2.1.0 to 2.1.1 in /web (#2076) 2022-01-07 09:14:23 +01:00
10570ac7f8 build(deps): bump goauthentik.io/api from 0.2021124.9 to 0.2021125.1 (#2077) 2022-01-07 09:14:07 +01:00
dc5667b0b8 web: Update Web API Client version (#2073)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-06 22:23:23 +01:00
ec9cacb610 ci: post-release cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 22:22:50 +01:00
0027dbc0e5 root: remove old api path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 22:21:21 +01:00
c15e4b24a1 release: 2021.12.5 2022-01-06 21:29:12 +01:00
b6f518ffe6 lifecycle: fix tests in container not working
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 21:29:08 +01:00
4e476fd4e9 website/docs: update 2021.12.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 21:15:27 +01:00
03503363e5 core: fix UserSelfSerializer's save() overwriting other user attributes
closes #2070

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 18:23:06 +01:00
22d6621b02 root run backup every 24 hours
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 15:29:11 +01:00
0023df64c8 root: bump python packages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-06 14:31:54 +01:00
59a259e43a build(deps): bump @rollup/plugin-node-resolve in /web (#2066) 2022-01-06 08:48:54 +01:00
c6f39f5eb4 build(deps): bump lit from 2.0.2 to 2.1.0 in /web (#2067) 2022-01-06 08:48:27 +01:00
e3c0aad48a build(deps): bump goauthentik.io/api from 0.2021124.8 to 0.2021124.9 (#2068) 2022-01-06 08:48:07 +01:00
91dd33cee6 policies/reputation: trigger save on update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-05 22:06:20 +01:00
5a2c367e89 policies/reputation: fix test
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-05 21:44:15 +01:00
3b05c9cb1a web: Update Web API Client version (#2065)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-05 21:18:19 +01:00
6e53f1689d policies/reputation: rework reputation to use a single entry, include geo_ip data
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-05 21:02:33 +01:00
e3be0f2550 Merge branch 'next' 2022-01-05 10:00:52 +01:00
294f2243c1 build(deps): bump rollup from 2.62.0 to 2.63.0 in /web (#2064) 2022-01-05 08:47:09 +01:00
7b1373e8d6 core: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 23:17:37 +01:00
e70b486f20 outposts: handle error in certificate cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 22:53:37 +01:00
b90174f153 root: use django-dbbackup 4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 22:17:07 +01:00
7d7acd8494 root: add ak wrapper script to be installed with poetry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 22:17:07 +01:00
4d9d7c5efb Translate /web/src/locales/en.po in tr (#2063)
translation completed for the source file '/web/src/locales/en.po'
on the 'tr' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-04 22:17:02 +01:00
d614b3608d root: use packaged version of django-dbbackup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 22:06:12 +01:00
beb2715fa7 root: bump python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 22:05:12 +01:00
5769ff45b5 core: add goauthentik.io/user/can-change-name
closes #2054

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 19:03:12 +01:00
9d6f79558f tenants: forbid creation of multiple default tenants
closes #2059

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 19:01:20 +01:00
41d5bff9d3 web/admin: fix delete form for tenants missing columns
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 18:54:56 +01:00
ec84ba9b6d website/docs: prepare 2021.12.5
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-04 18:35:45 +01:00
042a62f99e build(deps): bump @typescript-eslint/parser from 5.8.1 to 5.9.0 in /web (#2055) 2022-01-04 05:44:30 +01:00
907f02cfee core: compile backend translations (#2057) 2022-01-04 05:43:59 +01:00
53fe412bf9 build(deps): bump @typescript-eslint/eslint-plugin in /web (#2056) 2022-01-04 05:43:27 +01:00
ef9e177fe9 build(deps): bump goauthentik.io/api from 0.2021124.6 to 0.2021124.8 (#2058) 2022-01-04 05:42:52 +01:00
28e675596b web/flows: only add helper username input if using native shadow dom to prevent browser confusion
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 22:30:56 +01:00
9b7f57cc75 web/flows: add workaround for autofocus not working in password stage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 22:25:28 +01:00
935a8f4d58 core: add tests for non-applicable flows with flow manager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 22:14:52 +01:00
01fcbb325b website/integrations: add github org checking policy example
closes #2047

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 22:06:24 +01:00
7d3d17acb9 core: add error handling in source flow manager when flow isn't applicable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 21:57:55 +01:00
e434321f7c website/integrations: remove github url as they are auto-managed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 21:38:19 +01:00
ebd476be14 sources/oauth: fix sources not allowing blank values
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
2022-01-03 21:36:14 +01:00
31ba543c62 *: don't use exception keyword with structlog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 21:33:52 +01:00
a101d48b5a core: passthrough connection and additional data to FlowManager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
2022-01-03 21:31:26 +01:00
4c166dcf52 web/elements: re-enable codemirror line numbers (fixed on firefox)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 21:30:28 +01:00
47b1f025e1 web/admin: move additional scopes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 21:30:15 +01:00
8f44c792ac sources/oauth: fix github provider not including correct base scopes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
2022-01-03 21:04:18 +01:00
e57b6f2347 web/admin: mark additional scopes as non-required
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
2022-01-03 20:59:20 +01:00
275d0dfd03 web: Update Web API Client version (#2053)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-03 16:46:09 +01:00
f18cbace7a Translate /locale/en/LC_MESSAGES/django.po in de (#2052)
translation completed for the source file '/locale/en/LC_MESSAGES/django.po'
on the 'de' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2022-01-03 16:45:57 +01:00
212220554f sources/oauth: add additional scopes field to get additional data from provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2047
2022-01-03 16:43:52 +01:00
a596392bc3 web: Update Web API Client version (#2051)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-03 13:35:51 +01:00
3e22740eac core: add API endpoint to directly set user's password
closes #2040

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 13:31:58 +01:00
d18a691f63 core: prevent LDAP password being set for internal hash upgrades
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 13:23:42 +01:00
3cd5e68bc1 web/admin: add missing Okta label
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 12:36:21 +01:00
c741c13132 internal: fix listen attempt on shutdown
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-03 12:36:11 +01:00
924f6f104a build(deps): bump eslint from 8.5.0 to 8.6.0 in /web (#2048) 2022-01-03 10:34:36 +01:00
454594025b build(deps): bump goauthentik.io/api from 0.2021124.5 to 0.2021124.6 (#2049) 2022-01-03 10:34:19 +01:00
e72097292c web/flows: fix helper form not being removed from identification stage (improve password manager compatibility)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-02 20:03:34 +01:00
ab17a12184 web/user: fix auto-detected locale not being re-activated when switching to auto-detect
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-02 20:01:51 +01:00
776f3f69a5 core: compile backend translations (#2046) 2022-01-02 10:37:18 +01:00
8560c7150a Translate /locale/en/LC_MESSAGES/django.po in tr (#2044) 2022-01-02 00:15:18 +01:00
301386fb4a Translate /web/src/locales/en.po in tr (#2045) 2022-01-02 00:12:51 +01:00
68e8b6990b web: Update Web API Client version (#2043)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2022-01-01 20:28:23 +01:00
4f800c4758 web/flows: include user in access denied stage
closes #2039

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-01 20:25:49 +01:00
90c31c2214 flows: add test helpers to simplify and improve checking of stages, remove force_str
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-01 20:25:32 +01:00
50e3d317b2 flows: use WithUserInfoChallenge for AccessDeniedChallenge
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2039
2022-01-01 19:45:34 +01:00
3eed7bb010 lib: dont send any sentry events when testing
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-01 18:56:14 +01:00
0ef8edc9f1 web/user: add language selection
closes #2041

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-01-01 18:25:03 +01:00
a6373ebb33 web: fix tr locale not being loaded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-31 16:19:17 +01:00
bf8ce55eea web/admin: fix display when groups/users don't fit on a single row
closes #2030

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-31 12:03:21 +01:00
61b4fcb5f3 build(deps): bump @rollup/plugin-node-resolve in /web (#2032) 2021-12-31 08:54:30 +01:00
81275e3bd1 build(deps): bump @babel/preset-env from 7.16.5 to 7.16.7 in /web (#2033) 2021-12-31 08:54:13 +01:00
7988bf7748 build(deps): bump @babel/plugin-proposal-decorators in /web (#2034) 2021-12-31 08:54:03 +01:00
00d8eec360 build(deps): bump @babel/core from 7.16.5 to 7.16.7 in /web (#2035) 2021-12-31 08:53:08 +01:00
82150c8e84 build(deps): bump @babel/preset-typescript from 7.16.5 to 7.16.7 in /web (#2036) 2021-12-31 08:52:58 +01:00
1dbd749a74 build(deps): bump @babel/plugin-transform-runtime in /web (#2037) 2021-12-31 08:52:44 +01:00
a96479f16c build(deps): bump goauthentik.io/api from 0.2021124.3 to 0.2021124.5 (#2038) 2021-12-31 08:52:27 +01:00
5d5fb1f37e web/elements: fix alignment of chipgroup on modal add
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 22:37:53 +01:00
b6f4d6a5eb web/elements: fix spacing between chips in chip-group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2030
2021-12-30 22:34:55 +01:00
8ab5c04c2c web/admin: show flow title in list
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 22:10:31 +01:00
386944117e web: Update Web API Client version (#2031)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-30 22:02:52 +01:00
9154b9b85d web/user: rework user source connection UI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 21:59:41 +01:00
fc19372709 flows: fix migration removing flow titles
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 21:00:00 +01:00
e5d9c6537c web: add tr to locales
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 16:56:28 +01:00
bf5cbac314 web/admin: fix alignment in outpost list when expanding rows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 16:35:32 +01:00
5cca637a3d root: add opencontainer labels to dockerfiles
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 16:33:13 +01:00
5bfb8b454b web: fix broken links
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 16:27:16 +01:00
4d96437972 web: Update Web API Client version (#2028)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-30 15:19:13 +01:00
d03b0b8152 outposts: include outposts build hash in state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 15:16:34 +01:00
c249b55ff5 *: use py3.10 syntax for unions, remove old Type[] import when possible
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-30 14:59:01 +01:00
1e1876b34c root: bump python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-29 13:03:29 +01:00
a27493ad1b build(deps): bump @rollup/plugin-replace from 3.0.0 to 3.0.1 in /web (#2027)
Bumps [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/HEAD/packages/replace) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/replace/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/alias-v3.0.1/packages/replace)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-replace"
  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-12-29 12:27:35 +01:00
95b1ab820e build(deps): bump @typescript-eslint/eslint-plugin in /web (#2026) 2021-12-28 09:21:09 +01:00
5cf9f0002b build(deps): bump @typescript-eslint/parser from 5.8.0 to 5.8.1 in /web (#2025) 2021-12-28 09:15:39 +01:00
fc7a452b0c flows: update default flow titles
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-27 22:04:35 +01:00
25ee0e4b45 root: bump dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-27 20:45:15 +01:00
46f12e62e8 flows: don't create EventAction.FLOW_EXECUTION
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-27 15:07:33 +01:00
4245dea25a build(deps): bump rollup from 2.61.1 to 2.62.0 in /web (#2020) 2021-12-27 08:46:37 +01:00
908db3df81 build(deps): bump goauthentik.io/api from 0.2021124.2 to 0.2021124.3 (#2021) 2021-12-27 08:46:24 +01:00
ef4f9aa437 Translate /web/src/locales/en.po in tr (#2019)
translation completed updated for the source file '/web/src/locales/en.po'
on the 'tr' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2021-12-26 18:44:41 +01:00
902dd83c67 Translate /web/src/locales/en.po in tr (#2016)
translation completed updated for the source file '/web/src/locales/en.po'
on the 'tr' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2021-12-26 18:39:20 +01:00
1c4b78b5f4 Translate /web/src/locales/en.po in tr (#2005) 2021-12-26 18:37:10 +01:00
d854d819d1 web/flows: fix duplicate loading spinners when using webauthn
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 15:14:56 +01:00
f246da6b73 outposts/proxy: fix error checking for type assertion
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 14:57:32 +01:00
4a56b5e827 web: fix background for modals on light theme
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 14:53:23 +01:00
53b10e64f8 outposts: fix error when client hasn't be initialised
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 14:26:48 +01:00
27e4c7027c web: fix potential panic
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 14:24:44 +01:00
410d1b97cd outposts/proxy: add support for multiple states, when multiple requests are redirect at once
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-26 14:16:02 +01:00
f93f7e635b web: fix styling for modals, ensure correct classes are used
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 20:30:35 +01:00
74eba04735 web: remove page header colour, match user navbar to admin sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 19:46:53 +01:00
01bdaffe36 root: remove kubernetes version constraint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 19:23:31 +01:00
f6b556713a root: fix missing ssh directory from container
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 19:18:47 +01:00
abe38bb16a outposts: fix __exit__ being called without params
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 17:52:20 +01:00
f2b8d45999 web/admin: include key type in list
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 16:54:28 +01:00
3f61dff1cb web: Update Web API Client version (#1996)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-25 16:53:57 +01:00
b19da6d774 crypto: return private key's type (required for some oauth2 providers)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 16:51:28 +01:00
7c55616e29 outposts: fix creation of from_env docker client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 16:48:23 +01:00
952a7f07c1 website/docs: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 16:38:56 +01:00
6510b97c1e outposts: add remote docker integration via SSH
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 16:31:34 +01:00
19b707a0fb ci: fix translation command
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-25 13:42:08 +01:00
320a600349 root: migrate pipenv to poetry (#1995) 2021-12-24 23:25:38 +01:00
10110deae5 web/admin: add Admin in titlebar for admin interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-24 20:04:21 +01:00
884c546f32 outposts: clean up flow executor
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-24 19:52:19 +01:00
abec906677 root: bump python packages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-24 15:13:36 +01:00
22d1dd801c root: also use analytics uuid for sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-24 15:13:27 +01:00
03891cbe09 build(deps): bump chart.js from 3.6.2 to 3.7.0 in /web (#1993) 2021-12-24 09:50:16 +01:00
3c5157dfd4 build(deps): bump fuse.js from 6.5.0 to 6.5.3 in /web (#1992) 2021-12-24 09:49:31 +01:00
d241e8d51d build(deps): bump @types/chart.js from 2.9.34 to 2.9.35 in /web (#1991) 2021-12-24 09:49:14 +01:00
7ba15884ed build(deps): bump goauthentik.io/api from 0.2021123.3 to 0.2021124.2 (#1994) 2021-12-24 09:48:47 +01:00
47356915b1 outposts: fix outpost's sentry not sending release
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 19:01:32 +01:00
2520c92b78 website/docs: add additional docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:51:18 +01:00
e7e0e6d213 lib: strip values for timedelta from string
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:49:35 +01:00
ca0250e19f core: add meta theme-color
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:49:24 +01:00
cf4c7c1bcb web: fix missing closing tag
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:35:33 +01:00
670af8789a web: Update Web API Client version (#1990)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-23 18:29:32 +01:00
5c5634830f stages/identification: add field for passwordless flow
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:27:00 +01:00
b6b0edb7ad website/docs: use compose override for certbot instead separate stack
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 18:03:35 +01:00
45440abc80 web: Update Web API Client version (#1989)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-23 11:05:34 +01:00
9c42b75567 release: 2021.12.4 2021-12-23 10:32:48 +01:00
e9a477c1eb root: cleanup bumpversion config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 10:32:46 +01:00
fa60655a5d build(deps): bump github.com/getsentry/sentry-go from 0.11.0 to 0.12.0 (#1987) 2021-12-23 09:54:46 +01:00
5d729b4878 build(deps): bump fuse.js from 6.4.6 to 6.5.0 in /web (#1986) 2021-12-23 09:48:27 +01:00
8692f7233f build(deps): bump goauthentik.io/api from 0.2021123.2 to 0.2021123.3 (#1988) 2021-12-23 09:47:56 +01:00
457e17fec3 website/docs: add small let's encrypt docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 00:59:06 +01:00
87e99625e6 internal: update tenant certificates on outpost refresh
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-23 00:38:49 +01:00
6f32eeea43 website/docs: prepare 2021.12.4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 23:37:04 +01:00
dfcf8b2d40 root: update python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 23:36:56 +01:00
846006f2e3 events: create test notification with event with data
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 23:32:29 +01:00
f557b2129f *: fix random typos
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 23:13:18 +01:00
6dc2003e34 providers/oauth2: fix tests validating JWT incorrectly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 23:00:57 +01:00
0149c89003 providers/oauth2: fix invalid assignments in JWKS view
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 22:41:28 +01:00
f458cae954 providers/proxy: add error handing when field is already gone
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 22:31:53 +01:00
f01d117ce6 providers/proxy: fix imports in migrations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 22:25:02 +01:00
2bde43e5dc crypto: use older syntax for type union
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 22:22:45 +01:00
84cc0b5490 web: Update Web API Client version (#1984)
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-12-22 22:16:44 +01:00
2f3026084e providers/oauth2: remove jwt_alg field and set algorithm based on selected keypair, select HS256 when no keypair is selected
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 22:09:49 +01:00
89696edbee website/integrations: cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 21:46:46 +01:00
c1f0833c09 crypto: improve support for non-rsa private keys (discovery)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 21:46:22 +01:00
c77f804b77 web/user: fix user details not rendering when loading to a different user settings tab and then switching
closes #1664

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 20:13:52 +01:00
8e83209631 stages/authenticator_validate: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 18:14:35 +01:00
2e48e0cc2f stages/authenticator_validate: fix prompt not triggering when using in non-authentication context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 18:03:02 +01:00
e72f0ab160 stages/authenticator_validation: refuse passwordless flow if flow is not for authentication
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 18:02:43 +01:00
a3c681cc44 website/docs: cleanup old image names
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 17:38:09 +01:00
5b3a9e29fb stages/authenticator_validate: add passwordless login
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 17:34:46 +01:00
15803dc67d website/docs: revert traefik to not use header regex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 15:30:01 +01:00
ff37e064c9 build(deps): bump goauthentik.io/api from 0.2021123.1 to 0.2021123.2 (#1983)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.2021123.1 to 0.2021123.2.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.2021123.1...v0.2021123.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-22 11:58:06 +01:00
ef8e922e2a web: Update Web API Client version (#1982)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-22 11:51:15 +01:00
34b11524f1 tenants: add web certificate field, make authentik's core certificate configurable based on keypair
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 11:43:45 +01:00
9e2492be5c web/elements: fix link from notification drawer not working in user interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 10:49:10 +01:00
b3ba083ff0 internal: cleanup logging, remove duplicate code
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 10:33:21 +01:00
22a8603892 internal: add custom proxy certificates support to embedded outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 10:16:01 +01:00
d83d058a4b build(deps): bump @docusaurus/plugin-client-redirects in /website (#1980) 2021-12-22 09:47:58 +01:00
ec3fd4a3ab build(deps): bump @docusaurus/preset-classic in /website (#1979) 2021-12-22 09:41:01 +01:00
0764668b14 build(deps): bump goauthentik.io/api from 0.2021122.2 to 0.2021123.1 (#1981) 2021-12-22 09:40:00 +01:00
16b6c17305 Revert "policies: don't always clear application cache on post_save"
This reverts commit 5ef385f0bb.

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

# Conflicts:
#	authentik/policies/signals.py
2021-12-22 00:23:19 +01:00
e60509697a web/admin: fix explore integration not opening in new tab
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-22 00:03:28 +01:00
85364af9e9 web: Update Web API Client version (#1978)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-21 21:28:01 +01:00
cf4b4030aa release: 2021.12.3 2021-12-21 20:52:08 +01:00
74dc025869 ci: sentry release even when tests fail
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 20:52:03 +01:00
cabdc53553 root: fix compose docker image
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 20:51:39 +01:00
29e9f399bd website/docs: prepare 2021.12.3 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 19:50:24 +01:00
dad43017a0 web/admin: use SentryIgnoredError for user errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 19:44:44 +01:00
7fb939f97b core: fix error when getting launch URL for application with non-existent Provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 19:40:29 +01:00
Sem
88859b1c26 website/integrations: Updated Gitea Integration (#1972)
* Updated Gitea Integration

Described a fix to a situation where Gitea might require an additional OIDC mapping in order to make the authentication flow function properly.

* Update index.md

Updated as discussed in PR

* Update index.md

Implementing requested changes
2021-12-21 19:39:27 +01:00
c78236a2a2 root: don't set secure cross opener policy
closes #1977

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 19:16:22 +01:00
ba55538a34 outposts/proxy: cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 19:16:06 +01:00
f742c73e24 outposts/proxy: fix allowlist for forward_auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1970
2021-12-21 15:49:25 +01:00
ca314c262c *: revert to using GHCR directly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 13:54:49 +01:00
b932b6c963 website/docs: update log levels
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 13:15:17 +01:00
3c048a1921 outposts/proxy: fix session not expiring correctly due to miscalculation
closes #1976

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 13:10:57 +01:00
8a60a7e26f providers/proxy: revert to static list of forwarded headers
wildcard is not usable for this since the regular expression doesn't support negative lookahead, meaning we would always forward all headers, including Connection and others

closes #1969

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 12:04:54 +01:00
f10b57ba0b outposts/proxy: handle redirect loop in start handler, show error message
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-21 10:07:08 +01:00
e53114a645 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1974)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.8.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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-12-21 09:27:57 +01:00
2e50532518 build(deps): bump codemirror from 5.64.0 to 5.65.0 in /web (#1973) 2021-12-21 09:14:27 +01:00
1936ddfecb build(deps): bump @typescript-eslint/parser from 5.7.0 to 5.8.0 in /web (#1975) 2021-12-21 09:13:50 +01:00
4afef46cb8 ci: improve restore after switching to stable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:47:06 +01:00
92b4244e81 providers/proxy: update traefik regex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1969
2021-12-20 22:43:58 +01:00
dfbf7027bc providers/proxy: add traefik.ingress.kubernetes.io/router.tls annotation for ingress
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:24:42 +01:00
eca2ef20d0 outposts/proxy: add initial redirect-loop prevention
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:21:53 +01:00
cac5c7b3ea outposts/proxy: make templates more re-usable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:20:23 +01:00
37ee555c8e outposts/proxy: fix ping URI not being routed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:12:02 +01:00
f910da0f8a outposts: fix initial refresh not calling Server.Refresh()
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:47:32 +01:00
fc9d270992 outposts/ldap: fix log formatter and level not being set correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:46:01 +01:00
dcbc3d788a web/admin: fix border for outpost health status
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:40:26 +01:00
4658018a90 Revert "outposts: rename outpost"
This reverts commit a5c30fd9c7.
2021-12-20 21:37:31 +01:00
577b7ee515 providers/proxy: include auth headers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:37:22 +01:00
621773c1ea internal: rework global logging settings, embedded outpost no longer overwrites core, clean up double init
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:23:19 +01:00
3da526f20e root: allow trace log level to work for core/embedded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:11:47 +01:00
052e465041 outpost: re-run globalSetup when updating config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:08:03 +01:00
c843f18743 lib: add additional celery logger to sentry ignore
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 21:04:45 +01:00
80d0b14bb8 outposts: fix error when getting state for non-existent outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:44:47 +01:00
68637cf7cf outposts: handle/ignore http Abort handler
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:42:45 +01:00
82acba26af internal: fix sentry sample rate not applying to proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:42:26 +01:00
ff8a812823 web/admin: don't auto-select certificate for LDAP source verification
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:31:57 +01:00
7f5fed2aea web/admin: add outpost type to list
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:30:52 +01:00
a5c30fd9c7 outposts: rename outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 19:28:05 +01:00
ef23a0da52 outposts/proxy: fix traefik header regex to only match Remote- and X- headers to prevent websocket errors
closes #1969

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 13:30:19 +01:00
ba527e7141 root: drop redis cache sentry errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 13:12:14 +01:00
8edc254ab5 root: upgrade python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 10:45:38 +01:00
42627d21b0 build(deps): bump eslint from 8.4.1 to 8.5.0 in /web (#1966) 2021-12-20 08:48:45 +01:00
2479b157d0 build(deps): bump goauthentik.io/api from 0.2021121.1 to 0.2021122.2 (#1967) 2021-12-20 08:48:22 +01:00
602573f83f ci: fix label
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-19 13:44:34 +01:00
20c33fa011 web: Update Web API Client version (#1962) 2021-12-19 13:31:25 +01:00
8599d9efe0 web/admin: auto set the embedded outpost's authentik_host on first view
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-19 13:27:04 +01:00
8e6fcfe350 root: fix inconsistent URL quoting of redis URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 22:24:41 +01:00
558aa45201 web: Update Web API Client version (#1959)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-18 21:37:25 +01:00
e9910732bc release: 2021.12.2 2021-12-18 21:03:50 +01:00
246dd4b062 ci: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 21:03:47 +01:00
4425f8d183 ci: only build arm, amd64 on linux and darwin
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 21:02:20 +01:00
c410bb8c36 ci: fix dependency
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 20:50:59 +01:00
44f62a4773 website/docs: add 2021.12.2 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 20:39:16 +01:00
b6ff04694f providers/oauth2: don't rely on expiry task for access codes and refresh tokens
closes #1911

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 17:42:41 +01:00
d4ce0e8e41 web/elements: fix border between search buttons
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 17:34:28 +01:00
362d72da8c ci: fix shouldBuild being based off of harbor
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 17:01:51 +01:00
88d0f8d8a8 web: Update Web API Client version (#1958)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-18 16:19:18 +01:00
61097b9400 policies/password: add minimum digits
closes #1952

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 16:15:56 +01:00
7a73ddfb60 outposts/proxy: match skipPathRegex against full URL on domain auth
closes #1955

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 15:50:42 +01:00
d66f13c249 web: Update Web API Client version (#1957)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-18 15:38:52 +01:00
8cc3cb6a42 website/docs: fix reference to non-existent version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 15:34:39 +01:00
4c5537ddfe sources/oauth: allow writing to user in SourceConnection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1888
2021-12-18 15:33:46 +01:00
a95779157d tests/integration: add rename and full update tests for k8s controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 15:32:16 +01:00
70256727fd web: ignore instantSearchSDKJSBridgeClearHighlight error on edge on iOS
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 15:05:40 +01:00
ac6afb2b82 stages/email: add test for non-existent directory
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-18 15:05:40 +01:00
2ea7bd86e8 website/integrations: add Service Provider Binding: Post for rancher (#1956)
Adds a line to the docs that's important for Rancher and Authentik to work
2021-12-18 15:05:27 +01:00
95bce9c9e7 outposts: release binary outposts (#1954)
* outposts/proxy: always embed static assets, still check local

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

* ci: add initial ci to build outpost as binary

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

* ci: fix typo, build web

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

* ci: upload to release on publish, only run linux on ci

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

* ci: ensure latest go is used

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

* ci: split e2e tests into two halves

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-17 19:49:32 +00:00
71a22c2a34 outposts: add unittests for docker controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-17 13:42:33 +01:00
f3eb85877d web: Update Web API Client version (#1951)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-17 12:02:22 +01:00
273f5211a0 providers/saml: Fix typo (#1950) 2021-12-17 11:00:20 +00:00
db06428ab9 build(deps): bump goauthentik.io/api from 0.2021104.17 to 0.2021121.1 (#1948)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.2021104.17 to 0.2021121.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.2021104.17...v0.2021121.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-17 10:21:58 +01:00
109d8e48d4 website: update integrations categories to match new version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 21:38:37 +01:00
2ca115285c crypto: fix private keys not being imported correctly
closes #1945

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 21:14:15 +01:00
f5459645a5 web/admin: fix background colour for application sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 21:06:08 +01:00
14c159500d core: don't rotate non-api tokens
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 19:32:39 +01:00
03da87991f outposts: don't use custom environment
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 19:12:05 +01:00
e38ee9c580 web: Update Web API Client version (#1944)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-16 16:56:43 +01:00
3bf53b2db1 root: update security
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 16:54:48 +01:00
435 changed files with 37349 additions and 9420 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.12.1
current_version = 2022.1.5
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
@ -17,7 +17,7 @@ values =
beta
stable
[bumpversion:file:website/docs/installation/docker-compose.md]
[bumpversion:file:pyproject.toml]
[bumpversion:file:docker-compose.yml]
@ -30,7 +30,3 @@ values =
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:web/src/constants.ts]
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]
[bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md]

View File

@ -33,40 +33,36 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v2
with:
node-version: '16'
- id: cache-pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run pylint
run: pipenv run make ci-${{ matrix.job }}
- name: run job
run: poetry run make ci-${{ matrix.job }}
test-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations
run: pipenv run python -m lifecycle.migrate
run: poetry run python -m lifecycle.migrate
test-migrations-from-stable:
runs-on: ubuntu-latest
steps:
@ -74,71 +70,69 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: prepare variables
id: ev
run: |
python ./scripts/gh_env.py
- id: cache-pipenv
sudo pip install -U pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: checkout stable
run: |
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
cp -R .github ..
cp -R scripts ..
cp -R poetry.lock pyproject.toml ..
git checkout $(git describe --abbrev=0 --match 'version/*')
rm -rf .github/ scripts/
mv ../.github ../scripts .
mv ../.github ../scripts ../poetry.lock ../pyproject.toml .
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
# Sync anyways since stable will have different dependencies
pipenv sync --dev
# install anyways since stable will have different dependencies
poetry install
- name: run migrations to stable
run: pipenv run python -m lifecycle.migrate
run: poetry run python -m lifecycle.migrate
- name: checkout current code
run: |
set -x
git fetch
git reset --hard HEAD
git checkout $GITHUB_HEAD_REF
pipenv sync --dev
git checkout $GITHUB_SHA
poetry install
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: migrate to latest
run: pipenv run python -m lifecycle.migrate
run: poetry 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
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.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
poetry run make test
poetry run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
@ -150,16 +144,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
@ -168,21 +160,19 @@ jobs:
uses: helm/kind-action@v1.2.0
- name: run integration
run: |
pipenv run make test-integration
pipenv run coverage xml
poetry run make test-integration
poetry run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [integration]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
test-e2e:
test-e2e-provider:
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'
@ -191,14 +181,14 @@ jobs:
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- id: cache-pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/docker-compose.yml up -d
@ -215,12 +205,57 @@ jobs:
npm run build
- name: run e2e
run: |
pipenv run make test-e2e
pipenv run coverage xml
poetry run make test-e2e-provider
poetry run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [e2e]unittest.xml --link=codecov
testspace [e2e-provider]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
test-e2e-rest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- 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-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
uses: actions/cache@v2.1.7
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: |
poetry run make test-e2e-rest
poetry run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [e2e-rest]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
ci-core-mark:
@ -230,7 +265,8 @@ jobs:
- test-migrations-from-stable
- test-unittest
- test-integration
- test-e2e
- test-e2e-rest
- test-e2e-provider
runs-on: ubuntu-latest
steps:
- run: echo mark
@ -252,7 +288,7 @@ jobs:
- name: prepare variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
python ./scripts/gh_env.py
- name: Login to Container Registry

View File

@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16.3'
go-version: "^1.17"
- name: Run linter
run: |
# Create folder structure for go embeds
@ -28,11 +28,27 @@ jobs:
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci/golangci-lint:v1.43 \
golangci-lint run -v --timeout 200s
test-unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.17"
- name: Get dependencies
run: |
go get github.com/axw/gocov/gocov
go get github.com/AlekSi/gocov-xml
go get github.com/jstemmer/go-junit-report
- name: Go unittests
run: |
go test -timeout 0 -v -race -coverprofile=coverage.out -covermode=atomic -cover ./... | go-junit-report > junit.xml
ci-outpost-mark:
needs:
- lint-golint
- test-unittest
runs-on: ubuntu-latest
steps:
- run: echo mark
@ -58,7 +74,7 @@ jobs:
- name: prepare variables
id: ev
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
python ./scripts/gh_env.py
- name: Login to Container Registry
@ -80,3 +96,41 @@ jobs:
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: ${{ matrix.arch }}
build-outpost-binary:
timeout-minutes: 120
needs:
- ci-outpost-mark
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
type:
- proxy
- ldap
goos: [linux]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.17"
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Build web
run: |
cd web
npm install
npm run build-proxy
- name: Build outpost
run: |
set -x
export GOOS=${{ matrix.goos }}
export GOARCH=${{ matrix.goarch }}
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- uses: actions/upload-artifact@v2
with:
name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
path: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@ -30,14 +30,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.12.1,
beryju/authentik:2022.1.5,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.12.1,
ghcr.io/goauthentik/server:2022.1.5,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.12.1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.15"
go-version: "^1.17"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
- name: Set up Docker Buildx
@ -78,14 +78,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-${{ matrix.type }}:2021.12.1,
beryju/authentik-${{ matrix.type }}:2022.1.5,
beryju/authentik-${{ matrix.type }}:latest,
ghcr.io/goauthentik/${{ matrix.type }}:2021.12.1,
ghcr.io/goauthentik/${{ matrix.type }}:2022.1.5,
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.12.1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }}
run: |
docker pull beryju/authentik-${{ matrix.type }}:latest
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
@ -93,10 +93,50 @@ jobs:
docker pull ghcr.io/goauthentik/${{ matrix.type }}:latest
docker tag ghcr.io/goauthentik/${{ matrix.type }}:latest ghcr.io/goauthentik/${{ matrix.type }}:stable
docker push ghcr.io/goauthentik/${{ matrix.type }}:stable
build-outpost-binary:
timeout-minutes: 120
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
type:
- proxy
- ldap
goos: [linux, darwin]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.17"
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Build web
run: |
cd web
npm install
npm run build-proxy
- name: Build outpost
run: |
set -x
export GOOS=${{ matrix.goos }}
export GOARCH=${{ matrix.goarch }}
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
asset_name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
tag: ${{ github.ref }}
test-release:
needs:
- build-server
- build-outpost
- build-outpost-binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -110,7 +150,9 @@ jobs:
docker-compose run -u root server test
sentry-release:
needs:
- test-release
- build-server
- build-outpost
- build-outpost-binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -128,7 +170,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.12.1
version: authentik@2022.1.5
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -22,22 +22,20 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
- id: cache-poetry
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-cache-v2-${{ hashFiles('**/poetry.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
INSTALL: ${{ steps.cache-poetry.outputs.cache-hit }}
run: |
sudo apt-get update
sudo apt-get install -y gettext
scripts/ci_prepare.sh
- name: run compile
run: pipenv run ./manage.py compilemessages
run: poetry run ./manage.py compilemessages
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
id: cpr

View File

@ -1 +0,0 @@
3.9.7

View File

@ -11,7 +11,9 @@
"saml",
"totp",
"webauthn",
"traefik"
"traefik",
"passwordless",
"kubernetes"
],
"python.linting.pylintEnabled": true,
"todo-tree.tree.showCountsInTree": true,

View File

@ -1,16 +1,4 @@
# Stage 1: Lock python dependencies
FROM docker.io/python:3.10.1-slim-bullseye as locker
COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/
WORKDIR /app/
RUN pip install pipenv && \
pipenv lock -r > requirements.txt && \
pipenv lock -r --dev-only > requirements-dev.txt
# Stage 2: Build website
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as website-builder
COPY ./website /work/website/
@ -18,7 +6,7 @@ COPY ./website /work/website/
ENV NODE_ENV=production
RUN cd /work/website && npm i && npm run build-docs-only
# Stage 3: Build webui
# Stage 2: Build webui
FROM --platform=${BUILDPLATFORM} docker.io/node:16 as web-builder
COPY ./web /work/web/
@ -27,8 +15,8 @@ COPY ./website /work/website/
ENV NODE_ENV=production
RUN cd /work/web && npm i && npm run build
# Stage 4: Build go proxy
FROM docker.io/golang:1.17.5-bullseye AS builder
# Stage 3: Build go proxy
FROM docker.io/golang:1.17.6-bullseye AS builder
WORKDIR /work
@ -43,29 +31,38 @@ COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
FROM docker.io/python:3.10.1-slim-bullseye
# Stage 4: Run
FROM docker.io/python:3.10.2-slim-bullseye
LABEL org.opencontainers.image.url https://goauthentik.io
LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info.
LABEL org.opencontainers.image.source https://github.com/goauthentik/authentik
WORKDIR /
COPY --from=locker /app/requirements.txt /
COPY --from=locker /app/requirements-dev.txt /
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
COPY ./pyproject.toml /
COPY ./poetry.lock /
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl ca-certificates gnupg git runit libpq-dev \
postgresql-client build-essential libxmlsec1-dev \
pkg-config libmaxminddb0 && \
pip install -r /requirements.txt --no-cache-dir && \
pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-dev && \
rm -rf ~/.cache/pypoetry && \
apt-get remove --purge -y build-essential git && \
apt-get autoremove --purge -y && \
apt-get clean && \
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
mkdir -p /backups /certs /media && \
chown authentik:authentik /backups /certs /media
mkdir -p /authentik/.ssh && \
chown authentik:authentik /backups /certs /media /authentik/.ssh
COPY ./authentik/ /authentik
COPY ./pyproject.toml /

View File

@ -9,8 +9,14 @@ all: lint-fix lint test gen web
test-integration:
coverage run manage.py test tests/integration
test-e2e:
coverage run manage.py test tests/e2e
test-e2e-provider:
coverage run manage.py test tests/e2e/test_provider*
test-e2e-rest:
coverage run manage.py test tests/e2e/test_flows* tests/e2e/test_source*
test-go:
go test -timeout 0 -v -race -cover ./...
test:
coverage run manage.py test authentik
@ -32,6 +38,7 @@ lint-fix:
lint:
bandit -r authentik tests lifecycle -x node_modules
pylint authentik tests lifecycle
golangci-lint run -v
i18n-extract: i18n-extract-core web-extract
@ -102,20 +109,24 @@ web-extract:
# These targets are use by GitHub actions to allow usage of matrix
# which makes the YAML File a lot smaller
ci-pylint:
ci--meta-debug:
python -V
node --version
ci-pylint: ci--meta-debug
pylint authentik tests lifecycle
ci-black:
ci-black: ci--meta-debug
black --check authentik tests lifecycle
ci-isort:
ci-isort: ci--meta-debug
isort --check authentik tests lifecycle
ci-bandit:
ci-bandit: ci--meta-debug
bandit -r authentik tests lifecycle
ci-pyright:
ci-pyright: ci--meta-debug
pyright e2e lifecycle
ci-pending-migrations:
ci-pending-migrations: ci--meta-debug
./manage.py makemigrations --check

68
Pipfile
View File

@ -1,68 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[packages]
boto3 = "*"
celery = "*"
channels = "*"
channels-redis = "*"
codespell = "*"
colorama = "*"
dacite = "*"
deepmerge = "*"
defusedxml = "*"
django = "*"
django-dbbackup = { git = 'https://github.com/django-dbbackup/django-dbbackup.git', ref = '9d1909c30a3271c8c9c8450add30d6e0b996e145' }
django-filter = "*"
django-guardian = "*"
django-model-utils = "*"
django-otp = "*"
django-prometheus = "*"
django-redis = "*"
django-storages = "*"
djangorestframework = "*"
djangorestframework-guardian = "*"
docker = "*"
drf-spectacular = "*"
duo-client = "*"
facebook-sdk = "*"
geoip2 = "*"
gunicorn = "*"
kubernetes = "==v19.15.0"
ldap3 = "*"
lxml = "*"
packaging = "*"
psycopg2-binary = "*"
pycryptodome = "*"
pyjwt = "*"
pyyaml = "*"
requests-oauthlib = "*"
sentry-sdk = { git = 'https://github.com/beryju/sentry-python.git', ref = '379aee28b15d3b87b381317746c4efd24b3d7bc3' }
service_identity = "*"
structlog = "*"
swagger-spec-validator = "*"
twisted = "==21.7.0"
ua-parser = "*"
urllib3 = {extras = ["secure"],version = "*"}
uvicorn = {extras = ["standard"],version = "*"}
webauthn = "*"
xmlsec = "*"
flower = "*"
wsproto = "*"
[dev-packages]
bandit = "*"
black = "==21.11b1"
bump2version = "*"
colorama = "*"
coverage = {extras = ["toml"],version = "*"}
pylint = "*"
pylint-django = "*"
pytest = "*"
pytest-django = "*"
pytest-randomly = "*"
requests-mock = "*"
selenium = "*"
importlib-metadata = "*"

2505
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -57,4 +57,4 @@ DigitalOcean provides development and testing resources for authentik.
</a>
</p>
Netlify hosts the [goauthentik.io](goauthentik.io) site.
Netlify hosts the [goauthentik.io](https://goauthentik.io) site.

View File

@ -6,8 +6,8 @@
| Version | Supported |
| ---------- | ------------------ |
| 2021.9.x | :white_check_mark: |
| 2021.10.x | :white_check_mark: |
| 2021.12.x | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -1,3 +1,19 @@
"""authentik"""
__version__ = "2021.12.1"
from os import environ
from typing import Optional
__version__ = "2022.1.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
def get_build_hash(fallback: Optional[str] = None) -> str:
"""Get build hash"""
return environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "")
def get_full_version() -> str:
"""Get full version, with build hash appended"""
version = __version__
if (build_hash := get_build_hash()) != "":
version += "." + build_hash
return version

View File

@ -95,7 +95,7 @@ class TaskViewSet(ViewSet):
_("Successfully re-scheduled Task %(name)s!" % {"name": task.task_name}),
)
return Response(status=204)
except ImportError: # pragma: no cover
except (ImportError, AttributeError): # pragma: no cover
# if we get an import error, the module path has probably changed
task.delete()
return Response(status=500)

View File

@ -1,6 +1,4 @@
"""authentik administration overview"""
from os import environ
from django.core.cache import cache
from drf_spectacular.utils import extend_schema
from packaging.version import parse
@ -10,7 +8,7 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.api.utils import PassiveSerializer
@ -25,7 +23,7 @@ class VersionSerializer(PassiveSerializer):
def get_build_hash(self, _) -> str:
"""Get build hash, if version is not latest or released"""
return environ.get(ENV_GIT_HASH_KEY, "")
return get_build_hash()
def get_version_current(self, _) -> str:
"""Get current version"""

View File

@ -1,4 +1,6 @@
"""authentik admin app config"""
from importlib import import_module
from django.apps import AppConfig
@ -13,3 +15,4 @@ class AuthentikAdminConfig(AppConfig):
from authentik.admin.tasks import clear_update_notifications
clear_update_notifications.delay()
import_module("authentik.admin.signals")

View File

@ -0,0 +1,23 @@
"""admin signals"""
from django.dispatch import receiver
from authentik.admin.api.tasks import TaskInfo
from authentik.admin.api.workers import GAUGE_WORKERS
from authentik.root.celery import CELERY_APP
from authentik.root.monitoring import monitoring_set
@receiver(monitoring_set)
# pylint: disable=unused-argument
def monitoring_set_workers(sender, **kwargs):
"""Set worker gauge"""
count = len(CELERY_APP.control.ping(timeout=0.5))
GAUGE_WORKERS.set(count)
@receiver(monitoring_set)
# pylint: disable=unused-argument
def monitoring_set_tasks(sender, **kwargs):
"""Set task gauges"""
for task in TaskInfo.all().values():
task.set_prom_metrics()

View File

@ -1,6 +1,5 @@
"""authentik admin tasks"""
import re
from os import environ
from django.core.cache import cache
from django.core.validators import URLValidator
@ -9,7 +8,7 @@ from prometheus_client import Info
from requests import RequestException
from structlog.stdlib import get_logger
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik import __version__, get_build_hash
from authentik.events.models import Event, EventAction, Notification
from authentik.events.monitored_tasks import (
MonitoredTask,
@ -36,7 +35,7 @@ def _set_prom_info():
{
"version": __version__,
"latest": cache.get(VERSION_CACHE_KEY, ""),
"build_hash": environ.get(ENV_GIT_HASH_KEY, ""),
"build_hash": get_build_hash(),
}
)

View File

@ -1,7 +1,7 @@
"""API Authentication"""
from base64 import b64decode
from binascii import Error
from typing import Any, Optional, Union
from typing import Any, Optional
from django.conf import settings
from rest_framework.authentication import BaseAuthentication, get_authorization_header
@ -69,7 +69,7 @@ def token_secret_key(value: str) -> Optional[User]:
class TokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Bearer authentication"""
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
def authenticate(self, request: Request) -> tuple[User, Any] | None:
"""Token-based authentication using HTTP Bearer authentication"""
auth = get_authorization_header(request)

View File

@ -30,7 +30,7 @@ function getCookie(name) {
window.addEventListener('DOMContentLoaded', (event) => {
const rapidocEl = document.querySelector('rapi-doc');
rapidocEl.addEventListener('before-try', (e) => {
e.detail.request.headers.append('X-CSRFToken', getCookie("authentik_csrf"));
e.detail.request.headers.append('X-authentik-CSRF', getCookie("authentik_csrf"));
});
});
</script>

View File

@ -4,7 +4,5 @@ from django.urls import include, path
from authentik.api.v3.urls import urlpatterns as v3_urls
urlpatterns = [
# TODO: Remove in 2022.1
path("v2beta/", include(v3_urls)),
path("v3/", include(v3_urls)),
]

View File

@ -80,7 +80,7 @@ class ConfigView(APIView):
config = ConfigSerializer(
{
"error_reporting": {
"enabled": CONFIG.y("error_reporting.enabled"),
"enabled": CONFIG.y("error_reporting.enabled") and not settings.DEBUG,
"environment": CONFIG.y("error_reporting.environment"),
"send_pii": CONFIG.y("error_reporting.send_pii"),
"traces_sample_rate": float(CONFIG.y("error_reporting.sample_rate", 0.4)),

View File

@ -46,11 +46,7 @@ from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet
from authentik.policies.expression.api import ExpressionPolicyViewSet
from authentik.policies.hibp.api import HaveIBeenPwendPolicyViewSet
from authentik.policies.password.api import PasswordPolicyViewSet
from authentik.policies.reputation.api import (
IPReputationViewSet,
ReputationPolicyViewSet,
UserReputationViewSet,
)
from authentik.policies.reputation.api import ReputationPolicyViewSet, ReputationViewSet
from authentik.providers.ldap.api import LDAPOutpostConfigViewSet, LDAPProviderViewSet
from authentik.providers.oauth2.api.provider import OAuth2ProviderViewSet
from authentik.providers.oauth2.api.scope import ScopeMappingViewSet
@ -151,8 +147,7 @@ router.register("policies/event_matcher", EventMatcherPolicyViewSet)
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
router.register("policies/password_expiry", PasswordExpiryPolicyViewSet)
router.register("policies/password", PasswordPolicyViewSet)
router.register("policies/reputation/users", UserReputationViewSet)
router.register("policies/reputation/ips", IPReputationViewSet)
router.register("policies/reputation/scores", ReputationViewSet)
router.register("policies/reputation", ReputationPolicyViewSet)
router.register("providers/all", ProviderViewSet)

View File

@ -1,13 +1,16 @@
"""Application API Views"""
from typing import Optional
from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from django.utils.functional import SimpleLazyObject
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.fields import ReadOnlyField, SerializerMethodField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@ -39,11 +42,22 @@ def user_app_cache_key(user_pk: str) -> str:
class ApplicationSerializer(ModelSerializer):
"""Application Serializer"""
launch_url = ReadOnlyField(source="get_launch_url")
launch_url = SerializerMethodField()
provider_obj = ProviderSerializer(source="get_provider", required=False)
meta_icon = ReadOnlyField(source="get_meta_icon")
def get_launch_url(self, app: Application) -> Optional[str]:
"""Allow formatting of launch URL"""
url = app.get_launch_url()
if not url:
return url
user = self.context["request"].user
if isinstance(user, SimpleLazyObject):
user._setup()
user = user._wrapped
return url % user.__dict__
class Meta:
model = Application

View File

@ -1,10 +1,9 @@
"""Tokens API Viewset"""
from typing import Any
from django.http.response import Http404
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_anonymous_user
from guardian.shortcuts import assign_perm, get_anonymous_user
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
@ -96,10 +95,12 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
def perform_create(self, serializer: TokenSerializer):
if not self.request.user.is_superuser:
return serializer.save(
instance = serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
assign_perm("authentik_core.view_token_key", self.request.user, instance)
return instance
return super().perform_create(serializer)
@permission_required("authentik_core.view_token_key")
@ -114,7 +115,5 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
def view_key(self, request: Request, identifier: str) -> Response:
"""Return token key and log access"""
token: Token = self.get_object()
if token.is_expired:
raise Http404
Event.new(EventAction.SECRET_VIEW, secret=token).from_http(request) # noqa # nosec
return Response(TokenViewSerializer({"key": token.key}).data)

View File

@ -3,6 +3,7 @@ from datetime import timedelta
from json import loads
from typing import Optional
from django.contrib.auth import update_session_auth_hash
from django.db.models.query import QuerySet
from django.db.transaction import atomic
from django.db.utils import IntegrityError
@ -46,6 +47,7 @@ from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER
from authentik.core.models import (
USER_ATTRIBUTE_CHANGE_EMAIL,
USER_ATTRIBUTE_CHANGE_NAME,
USER_ATTRIBUTE_CHANGE_USERNAME,
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
@ -134,6 +136,16 @@ class UserSelfSerializer(ModelSerializer):
raise ValidationError("Not allowed to change email.")
return email
def validate_name(self, name: str):
"""Check if the user is allowed to change their name"""
if self.instance.group_attributes().get(
USER_ATTRIBUTE_CHANGE_NAME, CONFIG.y_bool("default_user_change_name", True)
):
return name
if name != self.instance.name:
raise ValidationError("Not allowed to change name.")
return name
def validate_username(self, username: str):
"""Check if the user is allowed to change their username"""
if self.instance.group_attributes().get(
@ -144,6 +156,13 @@ class UserSelfSerializer(ModelSerializer):
raise ValidationError("Not allowed to change username.")
return username
def save(self, **kwargs):
if self.instance:
attributes: dict = self.instance.attributes
attributes.update(self.validated_data.get("attributes", {}))
self.validated_data["attributes"] = attributes
return super().save(**kwargs)
class Meta:
model = User
@ -359,6 +378,35 @@ class UserViewSet(UsedByMixin, ModelViewSet):
).data
return Response(serializer.initial_data)
@permission_required("authentik_core.reset_user_password")
@extend_schema(
request=inline_serializer(
"UserPasswordSetSerializer",
{
"password": CharField(required=True),
},
),
responses={
204: "",
400: "",
},
)
@action(detail=True, methods=["POST"])
# pylint: disable=invalid-name, unused-argument
def set_password(self, request: Request, pk: int) -> Response:
"""Set password for user"""
user: User = self.get_object()
try:
user.set_password(request.data.get("password"))
user.save()
except (ValidationError, IntegrityError) as exc:
LOGGER.debug("Failed to set password", exc=exc)
return Response(status=400)
if user.pk == request.user.pk and SESSION_IMPERSONATE_USER not in self.request.session:
LOGGER.debug("Updating session hash after password change")
update_session_auth_hash(self.request, user)
return Response(status=204)
@extend_schema(request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)})
@action(
methods=["PUT"],

View File

@ -15,7 +15,6 @@ import authentik.lib.models
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache

View File

@ -12,7 +12,6 @@ import authentik.core.models
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache

View File

@ -1,12 +1,13 @@
"""authentik core models"""
from datetime import timedelta
from hashlib import md5, sha256
from typing import Any, Optional, Type
from typing import Any, Optional
from urllib.parse import urlencode
from uuid import uuid4
from deepmerge import always_merger
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
@ -38,6 +39,7 @@ USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
@ -160,6 +162,22 @@ class User(GuardianUserMixin, AbstractUser):
self.password_change_date = now()
return super().set_password(password)
def check_password(self, raw_password: str) -> bool:
"""
Return a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
Slightly changed version which doesn't send a signal for such internal hash upgrades
"""
def setter(raw_password):
self.set_password(raw_password, signal=False)
# Password hash upgrades shouldn't be considered password changes.
self._password = None
self.save(update_fields=["password"])
return check_password(raw_password, self.password, setter)
@property
def uid(self) -> str:
"""Generate a globall unique UID, based on the user ID and the hashed secret key"""
@ -224,7 +242,7 @@ class Provider(SerializerModel):
raise NotImplementedError
@property
def serializer(self) -> Type[Serializer]:
def serializer(self) -> type[Serializer]:
"""Get serializer for this model"""
raise NotImplementedError
@ -270,8 +288,8 @@ class Application(PolicyBindingModel):
"""Get launch URL if set, otherwise attempt to get launch URL based on provider."""
if self.meta_launch_url:
return self.meta_launch_url
if self.provider:
return self.get_provider().launch_url
if provider := self.get_provider():
return provider.launch_url
return None
def get_provider(self) -> Optional[Provider]:
@ -456,6 +474,14 @@ class Token(ManagedModel, ExpiringModel):
"""Handler which is called when this object is expired."""
from authentik.events.models import Event, EventAction
if self.intent in [
TokenIntents.INTENT_RECOVERY,
TokenIntents.INTENT_VERIFICATION,
TokenIntents.INTENT_APP_PASSWORD,
]:
super().expire_action(*args, **kwargs)
return
self.key = default_token_key()
self.expires = default_token_duration()
self.save(*args, **kwargs)
@ -497,7 +523,7 @@ class PropertyMapping(SerializerModel, ManagedModel):
raise NotImplementedError
@property
def serializer(self) -> Type[Serializer]:
def serializer(self) -> type[Serializer]:
"""Get serializer for this model"""
raise NotImplementedError

View File

@ -1,6 +1,7 @@
"""authentik core signals"""
from typing import TYPE_CHECKING, Type
from typing import TYPE_CHECKING
from django.apps import apps
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
@ -11,6 +12,8 @@ from django.dispatch import receiver
from django.http.request import HttpRequest
from prometheus_client import Gauge
from authentik.root.monitoring import monitoring_set
# Arguments: user: User, password: str
password_changed = Signal()
@ -20,6 +23,17 @@ if TYPE_CHECKING:
from authentik.core.models import AuthenticatedSession, User
@receiver(monitoring_set)
# pylint: disable=unused-argument
def monitoring_set_models(sender, **kwargs):
"""set models gauges"""
for model in apps.get_models():
GAUGE_MODELS.labels(
model_name=model._meta.model_name,
app=model._meta.app_label,
).set(model.objects.count())
@receiver(post_save)
# pylint: disable=unused-argument
def post_save_application(sender: type[Model], instance, created: bool, **_):
@ -27,11 +41,6 @@ def post_save_application(sender: type[Model], instance, created: bool, **_):
from authentik.core.api.applications import user_app_cache_key
from authentik.core.models import Application
GAUGE_MODELS.labels(
model_name=sender._meta.model_name,
app=sender._meta.app_label,
).set(sender.objects.count())
if sender != Application:
return
if not created: # pragma: no cover
@ -62,7 +71,7 @@ def user_logged_out_session(sender, request: HttpRequest, user: "User", **_):
@receiver(pre_delete)
def authenticated_session_delete(sender: Type[Model], instance: "AuthenticatedSession", **_):
def authenticated_session_delete(sender: type[Model], instance: "AuthenticatedSession", **_):
"""Delete session when authenticated session is deleted"""
from authentik.core.models import AuthenticatedSession

View File

@ -1,6 +1,6 @@
"""Source decision helper"""
from enum import Enum
from typing import Any, Optional, Type
from typing import Any, Optional
from django.contrib import messages
from django.db import IntegrityError
@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Source, SourceUserMatchingModes, User, UserSourceConnection
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage
from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow, Stage, in_memory_stage
from authentik.flows.planner import (
PLAN_CONTEXT_PENDING_USER,
@ -24,6 +25,8 @@ from authentik.flows.planner import (
)
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.types import PolicyResult
from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -50,7 +53,10 @@ class SourceFlowManager:
identifier: str
connection_type: Type[UserSourceConnection] = UserSourceConnection
connection_type: type[UserSourceConnection] = UserSourceConnection
enroll_info: dict[str, Any]
policy_context: dict[str, Any]
def __init__(
self,
@ -64,6 +70,7 @@ class SourceFlowManager:
self.identifier = identifier
self.enroll_info = enroll_info
self._logger = get_logger().bind(source=source, identifier=identifier)
self.policy_context = {}
# pylint: disable=too-many-return-statements
def get_action(self, **kwargs) -> tuple[Action, Optional[UserSourceConnection]]:
@ -144,20 +151,23 @@ class SourceFlowManager:
except IntegrityError as exc:
self._logger.warning("failed to get action", exc=exc)
return redirect("/")
self._logger.debug("get_action() says", action=action, connection=connection)
if connection:
if action == Action.LINK:
self._logger.debug("Linking existing user")
return self.handle_existing_user_link(connection)
if action == Action.AUTH:
self._logger.debug("Handling auth user")
return self.handle_auth_user(connection)
if action == Action.ENROLL:
self._logger.debug("Handling enrollment of new user")
return self.handle_enroll(connection)
self._logger.debug("get_action", action=action, connection=connection)
try:
if connection:
if action == Action.LINK:
self._logger.debug("Linking existing user")
return self.handle_existing_user_link(connection)
if action == Action.AUTH:
self._logger.debug("Handling auth user")
return self.handle_auth_user(connection)
if action == Action.ENROLL:
self._logger.debug("Handling enrollment of new user")
return self.handle_enroll(connection)
except FlowNonApplicableException as exc:
self._logger.warning("Flow non applicable", exc=exc)
return self.error_handler(exc, exc.policy_result)
# Default case, assume deny
messages.error(
self.request,
error = (
_(
(
"Request to authenticate with %(source)s has been denied. Please authenticate "
@ -166,7 +176,17 @@ class SourceFlowManager:
% {"source": self.source.name}
),
)
return redirect(reverse("authentik_core:root-redirect"))
return self.error_handler(error)
def error_handler(
self, error: Exception, policy_result: Optional[PolicyResult] = None
) -> HttpResponse:
"""Handle any errors by returning an access denied stage"""
response = AccessDeniedResponse(self.request)
response.error_message = str(error)
if policy_result:
response.policy_result = policy_result
return response
# pylint: disable=unused-argument
def get_stages_to_append(self, flow: Flow) -> list[Stage]:
@ -179,7 +199,9 @@ class SourceFlowManager:
]
return []
def _handle_login_flow(self, flow: Flow, **kwargs) -> HttpResponse:
def _handle_login_flow(
self, flow: Flow, connection: UserSourceConnection, **kwargs
) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to
# authorize application
@ -193,8 +215,10 @@ class SourceFlowManager:
PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_SOURCE: self.source,
PLAN_CONTEXT_REDIRECT: final_redirect,
PLAN_CONTEXT_SOURCES_CONNECTION: connection,
}
)
kwargs.update(self.policy_context)
if not flow:
return HttpResponseBadRequest()
# We run the Flow planner here so we can pass the Pending user in the context
@ -220,7 +244,7 @@ class SourceFlowManager:
_("Successfully authenticated with %(source)s!" % {"source": self.source.name}),
)
flow_kwargs = {PLAN_CONTEXT_PENDING_USER: connection.user}
return self._handle_login_flow(self.source.authentication_flow, **flow_kwargs)
return self._handle_login_flow(self.source.authentication_flow, connection, **flow_kwargs)
def handle_existing_user_link(
self,
@ -264,8 +288,8 @@ class SourceFlowManager:
return HttpResponseBadRequest()
return self._handle_login_flow(
self.source.enrollment_flow,
connection,
**{
PLAN_CONTEXT_PROMPT: delete_none_keys(self.enroll_info),
PLAN_CONTEXT_SOURCES_CONNECTION: connection,
},
)

View File

@ -6,7 +6,6 @@ from os import environ
from boto3.exceptions import Boto3Error
from botocore.exceptions import BotoCoreError, ClientError
from dbbackup.db.exceptions import CommandConnectorError
from django.conf import settings
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core import management
@ -63,8 +62,6 @@ def should_backup() -> bool:
return False
if not CONFIG.y_bool("postgresql.backup.enabled"):
return False
if settings.DEBUG:
return False
return True

View File

@ -5,6 +5,8 @@
{% block head %}
<script src="{% static 'dist/admin/AdminInterface.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% endblock %}
{% block body %}

View File

@ -5,6 +5,8 @@
{% block head %}
<script src="{% static 'dist/user/UserInterface.js' %}" type="module"></script>
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
{% endblock %}
{% block body %}

View File

@ -1,6 +1,5 @@
"""Test Applications API"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import Application
@ -14,7 +13,9 @@ class TestApplicationsAPI(APITestCase):
def setUp(self) -> None:
self.user = create_test_admin_user()
self.allowed = Application.objects.create(name="allowed", slug="allowed")
self.allowed = Application.objects.create(
name="allowed", slug="allowed", meta_launch_url="https://goauthentik.io/%(username)s"
)
self.denied = Application.objects.create(name="denied", slug="denied")
PolicyBinding.objects.create(
target=self.denied,
@ -32,7 +33,7 @@ class TestApplicationsAPI(APITestCase):
)
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_str(response.content), {"messages": [], "passing": True})
self.assertJSONEqual(response.content.decode(), {"messages": [], "passing": True})
response = self.client.get(
reverse(
"authentik_api:application-check-access",
@ -40,14 +41,14 @@ class TestApplicationsAPI(APITestCase):
)
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_str(response.content), {"messages": ["dummy"], "passing": False})
self.assertJSONEqual(response.content.decode(), {"messages": ["dummy"], "passing": False})
def test_list(self):
"""Test list operation without superuser_full_list"""
self.client.force_login(self.user)
response = self.client.get(reverse("authentik_api:application-list"))
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"pagination": {
"next": 0,
@ -65,8 +66,8 @@ class TestApplicationsAPI(APITestCase):
"slug": "allowed",
"provider": None,
"provider_obj": None,
"launch_url": None,
"meta_launch_url": "",
"launch_url": f"https://goauthentik.io/{self.user.username}",
"meta_launch_url": "https://goauthentik.io/%(username)s",
"meta_icon": None,
"meta_description": "",
"meta_publisher": "",
@ -83,7 +84,7 @@ class TestApplicationsAPI(APITestCase):
reverse("authentik_api:application-list") + "?superuser_full_list=true"
)
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"pagination": {
"next": 0,
@ -101,8 +102,8 @@ class TestApplicationsAPI(APITestCase):
"slug": "allowed",
"provider": None,
"provider_obj": None,
"launch_url": None,
"meta_launch_url": "",
"launch_url": f"https://goauthentik.io/{self.user.username}",
"meta_launch_url": "https://goauthentik.io/%(username)s",
"meta_icon": None,
"meta_description": "",
"meta_publisher": "",

View File

@ -2,7 +2,6 @@
from json import loads
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
@ -28,5 +27,5 @@ class TestAuthenticatedSessionsAPI(APITestCase):
self.client.force_login(self.other_user)
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
self.assertEqual(response.status_code, 200)
body = loads(force_str(response.content))
body = loads(response.content.decode())
self.assertEqual(body["pagination"]["count"], 1)

View File

@ -1,6 +1,6 @@
"""authentik core models tests"""
from time import sleep
from typing import Callable, Type
from typing import Callable
from django.test import RequestFactory, TestCase
from django.utils.timezone import now
@ -27,7 +27,7 @@ class TestModels(TestCase):
self.assertFalse(token.is_expired)
def source_tester_factory(test_model: Type[Stage]) -> Callable:
def source_tester_factory(test_model: type[Stage]) -> Callable:
"""Test source"""
factory = RequestFactory()
@ -47,7 +47,7 @@ def source_tester_factory(test_model: Type[Stage]) -> Callable:
return tester
def provider_tester_factory(test_model: Type[Stage]) -> Callable:
def provider_tester_factory(test_model: type[Stage]) -> Callable:
"""Test provider"""
def tester(self: TestModels):

View File

@ -6,8 +6,12 @@ from guardian.utils import get_anonymous_user
from authentik.core.models import SourceUserMatchingModes, User
from authentik.core.sources.flow_manager import Action
from authentik.flows.models import Flow, FlowDesignation
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.callback import OAuthSourceFlowManager
@ -17,7 +21,7 @@ class TestSourceFlowManager(TestCase):
def setUp(self) -> None:
super().setUp()
self.source = OAuthSource.objects.create(name="test")
self.source: OAuthSource = OAuthSource.objects.create(name="test")
self.factory = RequestFactory()
self.identifier = generate_id()
@ -143,3 +147,34 @@ class TestSourceFlowManager(TestCase):
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()
def test_error_non_applicable_flow(self):
"""Test error handling when a source selected flow is non-applicable due to a policy"""
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_LINK
flow = Flow.objects.create(
name="test", slug="test", title="test", designation=FlowDesignation.ENROLLMENT
)
policy = ExpressionPolicy.objects.create(
name="false", expression="""ak_message("foo");return False"""
)
PolicyBinding.objects.create(
policy=policy,
target=flow,
order=0,
)
self.source.enrollment_flow = flow
self.source.save()
flow_manager = OAuthSourceFlowManager(
self.source,
get_request("/", user=AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
response = flow_manager.get_flow()
self.assertIsInstance(response, AccessDeniedResponse)
# pylint: disable=no-member
self.assertEqual(response.error_message, "foo")

View File

@ -30,6 +30,7 @@ class TestTokenAPI(APITestCase):
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, True)
self.assertTrue(self.user.has_perm("authentik_core.view_token_key", token))
def test_token_create_invalid(self):
"""Test token creation endpoint (invalid data)"""
@ -54,7 +55,9 @@ class TestTokenAPI(APITestCase):
def test_token_expire(self):
"""Test Token expire task"""
token: Token = Token.objects.create(expires=now(), user=get_anonymous_user())
token: Token = Token.objects.create(
expires=now(), user=get_anonymous_user(), intent=TokenIntents.INTENT_API
)
key = token.key
clean_expired_models.delay().get()
token.refresh_from_db()

View File

@ -2,9 +2,15 @@
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import USER_ATTRIBUTE_CHANGE_EMAIL, USER_ATTRIBUTE_CHANGE_USERNAME, User
from authentik.core.models import (
USER_ATTRIBUTE_CHANGE_EMAIL,
USER_ATTRIBUTE_CHANGE_NAME,
USER_ATTRIBUTE_CHANGE_USERNAME,
User,
)
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
from authentik.flows.models import FlowDesignation
from authentik.lib.generators import generate_key
from authentik.stages.email.models import EmailStage
from authentik.tenants.models import Tenant
@ -18,11 +24,28 @@ class TestUsersAPI(APITestCase):
def test_update_self(self):
"""Test update_self"""
self.admin.attributes["foo"] = "bar"
self.admin.save()
self.admin.refresh_from_db()
self.client.force_login(self.admin)
response = self.client.put(
reverse("authentik_api:user-update-self"), data={"username": "foo", "name": "foo"}
)
self.admin.refresh_from_db()
self.assertEqual(response.status_code, 200)
self.assertEqual(self.admin.attributes["foo"], "bar")
self.assertEqual(self.admin.username, "foo")
self.assertEqual(self.admin.name, "foo")
def test_update_self_name_denied(self):
"""Test update_self"""
self.admin.attributes[USER_ATTRIBUTE_CHANGE_NAME] = False
self.admin.save()
self.client.force_login(self.admin)
response = self.client.put(
reverse("authentik_api:user-update-self"), data={"username": "foo", "name": "foo"}
)
self.assertEqual(response.status_code, 400)
def test_update_self_username_denied(self):
"""Test update_self"""
@ -68,6 +91,18 @@ class TestUsersAPI(APITestCase):
)
self.assertEqual(response.status_code, 404)
def test_set_password(self):
"""Test Direct password set"""
self.client.force_login(self.admin)
new_pw = generate_key()
response = self.client.post(
reverse("authentik_api:user-set-password", kwargs={"pk": self.admin.pk}),
data={"password": new_pw},
)
self.assertEqual(response.status_code, 204)
self.admin.refresh_from_db()
self.assertTrue(self.admin.check_password(new_pw))
def test_recovery(self):
"""Test user recovery link (no recovery flow set)"""
flow = create_test_flow(FlowDesignation.RECOVERY)

View File

@ -29,3 +29,4 @@ class UserSettingSerializer(PassiveSerializer):
component = CharField()
title = CharField()
configure_url = CharField(required=False)
icon_url = CharField()

View File

@ -1,4 +1,6 @@
"""Crypto API Views"""
from typing import Optional
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate
@ -15,6 +17,7 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
@ -24,6 +27,8 @@ from authentik.crypto.managed import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
LOGGER = get_logger()
class CertificateKeyPairSerializer(ModelSerializer):
"""CertificateKeyPair Serializer"""
@ -31,6 +36,7 @@ class CertificateKeyPairSerializer(ModelSerializer):
cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True)
cert_subject = SerializerMethodField()
private_key_available = SerializerMethodField()
private_key_type = SerializerMethodField()
certificate_download_url = SerializerMethodField()
private_key_download_url = SerializerMethodField()
@ -43,6 +49,13 @@ class CertificateKeyPairSerializer(ModelSerializer):
"""Show if this keypair has a private key configured or not"""
return instance.key_data != "" and instance.key_data is not None
def get_private_key_type(self, instance: CertificateKeyPair) -> Optional[str]:
"""Get the private key's type, if set"""
key = instance.private_key
if key:
return key.__class__.__name__.replace("_", "").lower().replace("privatekey", "")
return None
def get_certificate_download_url(self, instance: CertificateKeyPair) -> str:
"""Get URL to download certificate"""
return (
@ -66,22 +79,30 @@ class CertificateKeyPairSerializer(ModelSerializer):
def validate_certificate_data(self, value: str) -> str:
"""Verify that input is a valid PEM x509 Certificate"""
try:
load_pem_x509_certificate(value.encode("utf-8"), default_backend())
except ValueError:
# Cast to string to fully load and parse certificate
# Prevents issues like https://github.com/goauthentik/authentik/issues/2082
str(load_pem_x509_certificate(value.encode("utf-8"), default_backend()))
except ValueError as exc:
LOGGER.warning("Failed to load certificate", exc=exc)
raise ValidationError("Unable to load certificate.")
return value
def validate_key_data(self, value: str) -> str:
"""Verify that input is a valid PEM RSA Key"""
"""Verify that input is a valid PEM Key"""
# Since this field is optional, data can be empty.
if value != "":
try:
load_pem_private_key(
str.encode("\n".join([x.strip() for x in value.split("\n")])),
password=None,
backend=default_backend(),
# Cast to string to fully load and parse certificate
# Prevents issues like https://github.com/goauthentik/authentik/issues/2082
str(
load_pem_private_key(
str.encode("\n".join([x.strip() for x in value.split("\n")])),
password=None,
backend=default_backend(),
)
)
except (ValueError, TypeError):
except (ValueError, TypeError) as exc:
LOGGER.warning("Failed to load private key", exc=exc)
raise ValidationError("Unable to load private key (possibly encrypted?).")
return value
@ -98,6 +119,7 @@ class CertificateKeyPairSerializer(ModelSerializer):
"cert_expiry",
"cert_subject",
"private_key_available",
"private_key_type",
"certificate_download_url",
"private_key_download_url",
"managed",

View File

@ -44,7 +44,7 @@ class CertificateBuilder:
"""Build self-signed certificate"""
one_day = datetime.timedelta(1, 0, 0)
self.__private_key = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
public_exponent=65537, key_size=4096, backend=default_backend()
)
self.__public_key = self.__private_key.public_key()
alt_names: list[x509.GeneralName] = [x509.DNSName(x) for x in subject_alt_names or []]

View File

@ -6,6 +6,11 @@ from uuid import uuid4
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ec import (
EllipticCurvePrivateKey,
EllipticCurvePublicKey,
)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate
@ -36,8 +41,8 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
)
_cert: Optional[Certificate] = None
_private_key: Optional[RSAPrivateKey] = None
_public_key: Optional[RSAPublicKey] = None
_private_key: Optional[RSAPrivateKey | EllipticCurvePrivateKey | Ed25519PrivateKey] = None
_public_key: Optional[RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey] = None
@property
def certificate(self) -> Certificate:
@ -49,14 +54,16 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
return self._cert
@property
def public_key(self) -> Optional[RSAPublicKey]:
def public_key(self) -> Optional[RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey]:
"""Get public key of the private key"""
if not self._public_key:
self._public_key = self.private_key.public_key()
return self._public_key
@property
def private_key(self) -> Optional[RSAPrivateKey]:
def private_key(
self,
) -> Optional[RSAPrivateKey | EllipticCurvePrivateKey | Ed25519PrivateKey]:
"""Get python cryptography PrivateKey instance"""
if not self._private_key and self.key_data != "":
try:

View File

@ -24,7 +24,7 @@ MANAGED_DISCOVERED = "goauthentik.io/crypto/discovered/%s"
def ensure_private_key_valid(body: str):
"""Attempt loading of an RSA Private key without password"""
"""Attempt loading of a PEM Private key without password"""
load_pem_private_key(
str.encode("\n".join([x.strip() for x in body.split("\n")])),
password=None,
@ -42,7 +42,7 @@ def ensure_certificate_valid(body: str):
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task
def certificate_discovery(self: MonitoredTask):
"""Discover and update certificates form the filesystem"""
"""Discover, import and update certificates from the filesystem"""
certs = {}
private_keys = {}
discovered = 0
@ -52,6 +52,9 @@ def certificate_discovery(self: MonitoredTask):
continue
if path.is_dir():
continue
# For certbot setups, we want to ignore archive.
if "archive" in file:
continue
# Support certbot's directory structure
if path.name in ["fullchain.pem", "privkey.pem"]:
cert_name = path.parent.name
@ -60,7 +63,7 @@ def certificate_discovery(self: MonitoredTask):
try:
with open(path, "r+", encoding="utf-8") as _file:
body = _file.read()
if "BEGIN RSA PRIVATE KEY" in body:
if "PRIVATE KEY" in body:
private_keys[cert_name] = ensure_private_key_valid(body)
else:
certs[cert_name] = ensure_certificate_valid(body)
@ -79,7 +82,7 @@ def certificate_discovery(self: MonitoredTask):
cert.certificate_data = cert_data
dirty = True
if name in private_keys:
if cert.key_data == private_keys[name]:
if cert.key_data != private_keys[name]:
cert.key_data = private_keys[name]
dirty = True
if dirty:

View File

@ -146,7 +146,7 @@ class TestCrypto(APITestCase):
client_secret=generate_key(),
authorization_flow=create_test_flow(),
redirect_uris="http://localhost",
rsa_key=keypair,
signing_key=keypair,
)
response = self.client.get(
reverse(
@ -191,9 +191,12 @@ class TestCrypto(APITestCase):
with CONFIG.patch("cert_discovery_dir", temp_dir):
# pyright: reportGeneralTypeIssues=false
certificate_discovery() # pylint: disable=no-value-for-parameter
self.assertTrue(
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo").exists()
)
keypair: CertificateKeyPair = CertificateKeyPair.objects.filter(
managed=MANAGED_DISCOVERED % "foo"
).first()
self.assertIsNotNone(keypair)
self.assertIsNotNone(keypair.certificate)
self.assertIsNotNone(keypair.private_key)
self.assertTrue(
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo.bar").exists()
)

View File

@ -15,12 +15,14 @@ from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import (
Event,
Notification,
NotificationSeverity,
NotificationTransport,
NotificationTransportError,
TransportMode,
)
from authentik.events.utils import get_user
class NotificationTransportSerializer(ModelSerializer):
@ -86,6 +88,12 @@ class NotificationTransportViewSet(UsedByMixin, ModelViewSet):
severity=NotificationSeverity.NOTICE,
body=f"Test Notification from transport {transport.name}",
user=request.user,
event=Event(
action="Test",
user=get_user(request.user),
app=self.__class__.__module__,
context={"foo": "bar"},
),
)
try:
response = NotificationTransportTestSerializer(

View File

@ -35,12 +35,11 @@ class GeoIPReader:
def __open(self):
"""Get GeoIP Reader, if configured, otherwise none"""
path = CONFIG.y("authentik.geoip")
path = CONFIG.y("geoip")
if path == "" or not path:
return
try:
reader = Reader(path)
self.__reader = reader
self.__reader = Reader(path)
self.__last_mtime = stat(path).st_mtime
LOGGER.info("Loaded GeoIP database", last_write=self.__last_mtime)
except OSError as exc:

View File

@ -19,7 +19,7 @@ def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Event = apps.get_model("authentik_events", "Event")
db_alias = schema_editor.connection.alias
for event in Event.objects.all():
for event in Event.objects.using(db_alias).all():
event.delete()
# Because event objects cannot be updated, we have to re-create them
event.pk = None

View File

@ -10,7 +10,7 @@ def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Event = apps.get_model("authentik_events", "Event")
db_alias = schema_editor.connection.alias
for event in Event.objects.all():
for event in Event.objects.using(db_alias).all():
event.delete()
# Because event objects cannot be updated, we have to re-create them
event.pk = None

View File

@ -4,7 +4,7 @@ from collections import Counter
from datetime import timedelta
from inspect import currentframe
from smtplib import SMTPException
from typing import TYPE_CHECKING, Optional, Type, Union
from typing import TYPE_CHECKING, Optional
from uuid import uuid4
from django.conf import settings
@ -190,7 +190,7 @@ class Event(ExpiringModel):
@staticmethod
def new(
action: Union[str, EventAction],
action: str | EventAction,
app: Optional[str] = None,
**kwargs,
) -> "Event":
@ -517,7 +517,7 @@ class NotificationWebhookMapping(PropertyMapping):
return "ak-property-mapping-notification-form"
@property
def serializer(self) -> Type["Serializer"]:
def serializer(self) -> type["Serializer"]:
from authentik.events.api.notification_mapping import NotificationWebhookMappingSerializer
return NotificationWebhookMappingSerializer

View File

@ -72,7 +72,7 @@ class WithUserInfoChallenge(Challenge):
pending_user_avatar = CharField()
class AccessDeniedChallenge(Challenge):
class AccessDeniedChallenge(WithUserInfoChallenge):
"""Challenge when a flow's active stage calls `stage_invalid()`."""
error_message = CharField(required=False)

View File

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

View File

@ -10,8 +10,8 @@ def add_title_for_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
"default-invalidation-flow": "Default Invalidation Flow",
"default-source-enrollment": "Welcome to authentik! Please select a username.",
"default-source-authentication": "Welcome to authentik!",
"default-provider-authorization-implicit-consent": "Default Provider Authorization Flow (implicit consent)",
"default-provider-authorization-explicit-consent": "Default Provider Authorization Flow (explicit consent)",
"default-provider-authorization-implicit-consent": "Redirecting to %(app)s",
"default-provider-authorization-explicit-consent": "Redirecting to %(app)s",
"default-password-change": "Change password",
}
db_alias = schema_editor.connection.alias

View File

@ -0,0 +1,27 @@
# Generated by Django 4.0 on 2021-12-27 21:03
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def update_title_for_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
slug_title_map = {
"default-provider-authorization-implicit-consent": "Redirecting to %(app)s",
"default-provider-authorization-explicit-consent": "Redirecting to %(app)s",
}
db_alias = schema_editor.connection.alias
Flow = apps.get_model("authentik_flows", "Flow")
for flow in Flow.objects.using(db_alias).all():
if flow.slug not in slug_title_map:
continue
flow.title = slug_title_map[flow.slug]
flow.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0020_flowtoken"),
]
operations = [migrations.RunPython(update_title_for_defaults)]

View File

@ -1,7 +1,7 @@
"""Flow models"""
from base64 import b64decode, b64encode
from pickle import dumps, loads # nosec
from typing import TYPE_CHECKING, Optional, Type
from typing import TYPE_CHECKING, Optional
from uuid import uuid4
from django.db import models
@ -63,7 +63,7 @@ class Stage(SerializerModel):
objects = InheritanceManager()
@property
def type(self) -> Type["StageView"]:
def type(self) -> type["StageView"]:
"""Return StageView class that implements logic for this stage"""
# This is a bit of a workaround, since we can't set class methods with setattr
if hasattr(self, "__in_memory_type"):
@ -86,7 +86,7 @@ class Stage(SerializerModel):
return f"Stage {self.name}"
def in_memory_stage(view: Type["StageView"]) -> Stage:
def in_memory_stage(view: type["StageView"]) -> Stage:
"""Creates an in-memory stage instance, based on a `view` as view."""
stage = Stage()
# Because we can't pickle a locally generated function,

View File

@ -4,7 +4,7 @@ from typing import Any, Optional
from django.core.cache import cache
from django.http import HttpRequest
from prometheus_client import Histogram
from prometheus_client import Gauge, Histogram
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
@ -16,7 +16,6 @@ from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine
from authentik.root.monitoring import UpdatingGauge
LOGGER = get_logger()
PLAN_CONTEXT_PENDING_USER = "pending_user"
@ -27,10 +26,9 @@ PLAN_CONTEXT_SOURCE = "source"
# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan
# was restored.
PLAN_CONTEXT_IS_RESTORED = "is_restored"
GAUGE_FLOWS_CACHED = UpdatingGauge(
GAUGE_FLOWS_CACHED = Gauge(
"authentik_flows_cached",
"Cached flows",
update_func=lambda: len(cache.keys("flow_*") or []),
)
HIST_FLOWS_PLAN_TIME = Histogram(
"authentik_flows_plan_time",
@ -152,7 +150,9 @@ class FlowPlanner:
engine.build()
result = engine.result
if not result.passing:
raise FlowNonApplicableException(",".join(result.messages))
exc = FlowNonApplicableException(",".join(result.messages))
exc.policy_result = result
raise exc
# User is passing so far, check if we have a cached plan
cached_plan_key = cache_key(self.flow, user)
cached_plan = cache.get(cached_plan_key, None)
@ -169,7 +169,6 @@ class FlowPlanner:
)
plan = self._build_plan(user, request, default_context)
cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT)
GAUGE_FLOWS_CACHED.update()
if not plan.bindings and not self.allow_empty_flows:
raise EmptyFlowException()
return plan

View File

@ -4,6 +4,9 @@ from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from structlog.stdlib import get_logger
from authentik.flows.planner import GAUGE_FLOWS_CACHED
from authentik.root.monitoring import monitoring_set
LOGGER = get_logger()
@ -14,6 +17,13 @@ def delete_cache_prefix(prefix: str) -> int:
return len(keys)
@receiver(monitoring_set)
# pylint: disable=unused-argument
def monitoring_set_flows(sender, **kwargs):
"""set flow gauges"""
GAUGE_FLOWS_CACHED.set(len(cache.keys("flow_*") or []))
@receiver(post_save)
@receiver(pre_delete)
# pylint: disable=unused-argument

View File

@ -1,4 +1,6 @@
"""authentik stage Base view"""
from typing import TYPE_CHECKING, Optional
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from django.http.request import QueryDict
@ -11,15 +13,19 @@ from structlog.stdlib import get_logger
from authentik.core.models import DEFAULT_AVATAR, User
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
ChallengeResponse,
ChallengeTypes,
ContextualFlowInfo,
HttpChallengeResponse,
WithUserInfoChallenge,
)
from authentik.flows.models import InvalidResponseAction
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
from authentik.flows.views.executor import FlowExecutorView
if TYPE_CHECKING:
from authentik.flows.views.executor import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
LOGGER = get_logger()
@ -28,11 +34,11 @@ LOGGER = get_logger()
class StageView(View):
"""Abstract Stage, inherits TemplateView but can be combined with FormView"""
executor: FlowExecutorView
executor: "FlowExecutorView"
request: HttpRequest = None
def __init__(self, executor: FlowExecutorView, **kwargs):
def __init__(self, executor: "FlowExecutorView", **kwargs):
self.executor = executor
super().__init__(**kwargs)
@ -43,6 +49,8 @@ class StageView(View):
other things besides the form display.
If no user is pending, returns request.user"""
if not self.executor.plan:
return self.request.user
if PLAN_CONTEXT_PENDING_USER_IDENTIFIER in self.executor.plan.context and for_display:
return User(
username=self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER_IDENTIFIER),
@ -108,9 +116,14 @@ class ChallengeStageView(StageView):
def format_title(self) -> str:
"""Allow usage of placeholder in flow title."""
return self.executor.flow.title % {
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, "")
}
if not self.executor.plan:
return self.executor.flow.title
try:
return self.executor.flow.title % {
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, "")
}
except ValueError:
return self.executor.flow.title
def _get_challenge(self, *args, **kwargs) -> Challenge:
with Hub.current.start_span(
@ -169,3 +182,27 @@ class ChallengeStageView(StageView):
stage_view=self,
)
return HttpChallengeResponse(challenge_response)
class AccessDeniedChallengeView(ChallengeStageView):
"""Used internally by FlowExecutor's stage_invalid()"""
error_message: Optional[str]
def __init__(self, executor: "FlowExecutorView", error_message: Optional[str] = None, **kwargs):
super().__init__(executor, **kwargs)
self.error_message = error_message
def get_challenge(self, *args, **kwargs) -> Challenge:
return AccessDeniedChallenge(
data={
"error_message": self.error_message or "Unknown error",
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-access-denied",
}
)
# This can never be reached since this challenge is created on demand and only the
# .get() method is called
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: # pragma: no cover
return self.executor.cancel()

View File

@ -0,0 +1,51 @@
"""Test helpers"""
from json import loads
from typing import Any, Optional
from django.http.response import HttpResponse
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow
class FlowTestCase(APITestCase):
"""Helpers for testing flows and stages."""
# pylint: disable=invalid-name
def assertStageResponse(
self,
response: HttpResponse,
flow: Optional[Flow] = None,
user: Optional[User] = None,
**kwargs,
) -> dict[str, Any]:
"""Assert various attributes of a stage response"""
raw_response = loads(response.content.decode())
self.assertIsNotNone(raw_response["component"])
self.assertIsNotNone(raw_response["type"])
if flow:
self.assertIn("flow_info", raw_response)
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
self.assertEqual(
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
)
# We don't check the flow title since it will most likely go
# through ChallengeStageView.format_title() so might not match 1:1
# self.assertEqual(raw_response["flow_info"]["title"], flow.title)
self.assertIsNotNone(raw_response["flow_info"]["title"])
if user:
self.assertEqual(raw_response["pending_user"], user.username)
self.assertEqual(raw_response["pending_user_avatar"], user.avatar)
for key, expected in kwargs.items():
self.assertEqual(raw_response[key], expected)
return raw_response
# pylint: disable=invalid-name
def assertStageRedirects(self, response: HttpResponse, to: str) -> dict[str, Any]:
"""Wrapper around assertStageResponse that checks for a redirect"""
return self.assertStageResponse(
response, component="xak-flow-redirect", to=to, type=ChallengeTypes.REDIRECT.value
)

View File

@ -4,16 +4,14 @@ from unittest.mock import MagicMock, PropertyMock, patch
from django.http import HttpRequest, HttpResponse
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
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.flows.planner import FlowPlan, FlowPlanner
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG
from authentik.policies.dummy.models import DummyPolicy
@ -37,7 +35,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse):
TO_STAGE_RESPONSE_MOCK = MagicMock(side_effect=to_stage_response)
class TestFlowExecutor(APITestCase):
class TestFlowExecutor(FlowTestCase):
"""Test executor"""
def setUp(self):
@ -90,18 +88,11 @@ class TestFlowExecutor(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": FlowNonApplicableException.__doc__,
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
self.assertStageResponse(
response,
flow=flow,
error_message=FlowNonApplicableException.__doc__,
component="ak-stage-access-denied",
)
@patch(
@ -283,14 +274,7 @@ class TestFlowExecutor(APITestCase):
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_reevaluate_keep(self):
"""Test planner with re-evaluate (everything is kept)"""
@ -360,14 +344,7 @@ class TestFlowExecutor(APITestCase):
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_reevaluate_remove_consecutive(self):
"""Test planner with re-evaluate (consecutive stages are removed)"""
@ -407,18 +384,7 @@ class TestFlowExecutor(APITestCase):
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
@ -441,31 +407,13 @@ class TestFlowExecutor(APITestCase):
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
# fourth request, this confirms the last stage (dummy4)
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_stageview_user_identifier(self):
"""Test PLAN_CONTEXT_PENDING_USER_IDENTIFIER"""
@ -532,35 +480,16 @@ class TestFlowExecutor(APITestCase):
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"password_fields": False,
"primary_action": "Log in",
"sources": [],
"show_source_labels": False,
"user_fields": [UserFields.E_MAIL],
},
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Log in",
sources=[],
show_source_labels=False,
user_fields=[UserFields.E_MAIL],
)
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")

View File

@ -1,5 +1,5 @@
"""base model tests"""
from typing import Callable, Type
from typing import Callable
from django.test import TestCase
@ -12,7 +12,7 @@ class TestModels(TestCase):
"""Generic model properties tests"""
def model_tester_factory(test_model: Type[Stage]) -> Callable:
def model_tester_factory(test_model: type[Stage]) -> Callable:
"""Test a form"""
def tester(self: TestModels):

View File

@ -1,5 +1,5 @@
"""stage view tests"""
from typing import Callable, Type
from typing import Callable
from django.test import RequestFactory, TestCase
@ -16,7 +16,7 @@ class TestViews(TestCase):
self.exec = FlowExecutorView(request=self.factory.get("/"))
def view_tester_factory(view_class: Type[StageView]) -> Callable:
def view_tester_factory(view_class: type[StageView]) -> Callable:
"""Test a form"""
def tester(self: TestViews):

View File

@ -2,7 +2,7 @@
from contextlib import contextmanager
from copy import deepcopy
from json import loads
from typing import Any, Type
from typing import Any
from dacite import from_dict
from dacite.exceptions import DaciteError
@ -87,7 +87,7 @@ class FlowImporter:
def _validate_single(self, entry: FlowBundleEntry) -> BaseSerializer:
"""Validate a single entry"""
model_app_label, model_name = entry.model.split(".")
model: Type[SerializerModel] = apps.get_model(model_app_label, model_name)
model: type[SerializerModel] = apps.get_model(model_app_label, model_name)
if not isinstance(model(), ALLOWED_MODELS):
raise EntryInvalidError(f"Model {model} not allowed")

View File

@ -10,7 +10,6 @@ from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect
from django.http.request import QueryDict
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls.base import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
@ -26,7 +25,6 @@ from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import Event, EventAction, cleanse_dict
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
ChallengeResponse,
ChallengeTypes,
@ -51,6 +49,7 @@ from authentik.flows.planner import (
FlowPlan,
FlowPlanner,
)
from authentik.flows.stage import AccessDeniedChallengeView
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
@ -371,12 +370,6 @@ class FlowExecutorView(APIView):
NEXT_ARG_NAME, "authentik_core:root-redirect"
)
self.cancel()
Event.new(
action=EventAction.FLOW_EXECUTION,
flow=self.flow,
designation=self.flow.designation,
successful=True,
).from_http(self.request)
return to_stage_response(self.request, redirect_with_qs(next_param))
def stage_ok(self) -> HttpResponse:
@ -412,21 +405,9 @@ class FlowExecutorView(APIView):
is a superuser."""
self._logger.debug("f(exec): Stage invalid")
self.cancel()
response = HttpChallengeResponse(
AccessDeniedChallenge(
{
"error_message": error_message,
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-access-denied",
"flow_info": {
"title": self.flow.title,
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
},
}
)
)
return to_stage_response(self.request, response)
challenge_view = AccessDeniedChallengeView(self, error_message)
challenge_view.request = self.request
return to_stage_response(self.request, challenge_view.get(self.request))
def cancel(self):
"""Cancel current execution and return a redirect"""

View File

@ -6,7 +6,7 @@ postgresql:
port: 5432
password: 'env://POSTGRES_PASSWORD'
backup:
enabled: true
enabled: false
s3_backup:
access_key: ""
secret_key: ""
@ -64,7 +64,7 @@ outposts:
# %(type)s: Outpost type; proxy, ldap, etc
# %(version)s: Current version; 2021.4.1
# %(build_hash)s: Build hash if you're running a beta version
container_image_base: goauthentik.io/%(type)s:%(version)s
container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
cookie_domain: null
disable_update_check: false
@ -78,6 +78,7 @@ footer_links:
- name: authentik Website
href: https://goauthentik.io/?utm_source=authentik
default_user_change_name: true
default_user_change_email: true
default_user_change_username: true

View File

@ -32,6 +32,7 @@ class BaseEvaluator:
self._globals = {
"regex_match": BaseEvaluator.expr_regex_match,
"regex_replace": BaseEvaluator.expr_regex_replace,
"list_flatten": BaseEvaluator.expr_flatten,
"ak_is_group_member": BaseEvaluator.expr_is_group_member,
"ak_user_by": BaseEvaluator.expr_user_by,
"ak_logger": get_logger(),
@ -40,6 +41,15 @@ class BaseEvaluator:
self._context = {}
self._filename = "BaseEvalautor"
@staticmethod
def expr_flatten(value: list[Any] | Any) -> Optional[Any]:
"""Flatten `value` if its a list"""
if isinstance(value, list):
if len(value) < 1:
return None
return value[0]
return value
@staticmethod
def expr_regex_match(value: Any, regex: str) -> bool:
"""Expression Filter to run re.search"""

View File

@ -97,7 +97,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
if "exc_info" in hint:
_, exc_value, _ = hint["exc_info"]
if isinstance(exc_value, ignored_classes):
LOGGER.debug("dropping exception", exception=exc_value)
LOGGER.debug("dropping exception", exc=exc_value)
return None
if "logger" in event:
if event["logger"] in [
@ -108,9 +108,13 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
"multiprocessing",
"django_redis",
"django.security.DisallowedHost",
"django_redis.cache",
"celery.backends.redis",
"celery.worker",
"paramiko.transport",
]:
return None
LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None))
if settings.DEBUG:
if settings.DEBUG or settings.TEST:
return None
return event

View File

@ -13,4 +13,4 @@ class TestSentry(TestCase):
def test_error_sent(self):
"""Test error sent"""
self.assertEqual({}, before_send({}, {"exc_info": (0, ValueError(), 0)}))
self.assertEqual(None, before_send({}, {"exc_info": (0, ValueError(), 0)}))

View File

@ -1,5 +1,5 @@
"""base model tests"""
from typing import Callable, Type
from typing import Callable
from django.test import TestCase
from rest_framework.serializers import BaseSerializer
@ -13,7 +13,7 @@ class TestModels(TestCase):
"""Generic model properties tests"""
def model_tester_factory(test_model: Type[Stage]) -> Callable:
def model_tester_factory(test_model: type[Stage]) -> Callable:
"""Test a form"""
def tester(self: TestModels):

View File

@ -1,5 +1,4 @@
"""http helpers"""
from os import environ
from typing import Any, Optional
from django.http import HttpRequest
@ -7,7 +6,7 @@ from requests.sessions import Session
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik import get_full_version
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
@ -75,8 +74,7 @@ def get_client_ip(request: Optional[HttpRequest]) -> str:
def authentik_user_agent() -> str:
"""Get a common user agent"""
build = environ.get(ENV_GIT_HASH_KEY, "tagged")
return f"authentik@{__version__} (build={build})"
return f"authentik@{get_full_version()}"
def get_http_session() -> Session:

View File

@ -2,7 +2,6 @@
import os
from importlib import import_module
from pathlib import Path
from typing import Union
from django.conf import settings
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
@ -30,7 +29,7 @@ def class_to_path(cls: type) -> str:
return f"{cls.__module__}.{cls.__name__}"
def path_to_class(path: Union[str, None]) -> Union[type, None]:
def path_to_class(path: str | None) -> type | None:
"""Import module and return class"""
if not path:
return None
@ -59,4 +58,6 @@ def get_env() -> str:
return "compose"
if CONFIG.y_bool("debug"):
return "dev"
if "AK_APPLIANCE" in os.environ:
return os.environ["AK_APPLIANCE"]
return "custom"

View File

@ -34,7 +34,7 @@ def timedelta_from_string(expr: str) -> datetime.timedelta:
key, value = duration_pair.split("=")
if key.lower() not in ALLOWED_KEYS:
continue
kwargs[key.lower()] = float(value)
kwargs[key.lower()] = float(value.strip())
if len(kwargs) < 1:
raise ValueError("No valid keys to pass to timedelta")
return datetime.timedelta(**kwargs)

View File

@ -1,5 +1,5 @@
"""Managed objects manager"""
from typing import Callable, Optional, Type
from typing import Callable, Optional
from structlog.stdlib import get_logger
@ -11,11 +11,11 @@ LOGGER = get_logger()
class EnsureOp:
"""Ensure operation, executed as part of an ObjectManager run"""
_obj: Type[ManagedModel]
_obj: type[ManagedModel]
_managed_uid: str
_kwargs: dict
def __init__(self, obj: Type[ManagedModel], managed_uid: str, **kwargs) -> None:
def __init__(self, obj: type[ManagedModel], managed_uid: str, **kwargs) -> None:
self._obj = obj
self._managed_uid = managed_uid
self._kwargs = kwargs
@ -32,7 +32,7 @@ class EnsureExists(EnsureOp):
def __init__(
self,
obj: Type[ManagedModel],
obj: type[ManagedModel],
managed_uid: str,
created_callback: Optional[Callable] = None,
**kwargs,

View File

@ -12,6 +12,7 @@ from rest_framework.response import Response
from rest_framework.serializers import JSONField, ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik import get_build_hash
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer, is_dict
@ -98,8 +99,12 @@ class OutpostHealthSerializer(PassiveSerializer):
last_seen = DateTimeField(read_only=True)
version = CharField(read_only=True)
version_should = CharField(read_only=True)
version_outdated = BooleanField(read_only=True)
build_hash = CharField(read_only=True, required=False)
build_hash_should = CharField(read_only=True, required=False)
class OutpostFilter(FilterSet):
"""Filter for Outposts"""
@ -116,6 +121,7 @@ class OutpostFilter(FilterSet):
"providers": ["isnull"],
"name": ["iexact", "icontains"],
"service_connection__name": ["iexact", "icontains"],
"managed": ["iexact", "icontains"],
}
@ -145,6 +151,8 @@ class OutpostViewSet(UsedByMixin, ModelViewSet):
"version": state.version,
"version_should": state.version_should,
"version_outdated": state.version_outdated,
"build_hash": state.build_hash,
"build_hash_should": get_build_hash(),
}
)
return Response(OutpostHealthSerializer(states, many=True).data)

View File

@ -55,6 +55,10 @@ class OutpostConsumer(AuthJsonConsumer):
first_msg = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_logger()
def connect(self):
super().connect()
uuid = self.scope["url_route"]["kwargs"]["pk"]
@ -65,7 +69,7 @@ class OutpostConsumer(AuthJsonConsumer):
)
if not outpost:
raise DenyConnection()
self.logger = get_logger().bind(outpost=outpost)
self.logger = self.logger.bind(outpost=outpost)
try:
self.accept()
except RuntimeError as exc:

View File

@ -1,15 +1,18 @@
"""Base Controller"""
from dataclasses import dataclass
from os import environ
from typing import Optional
from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik import __version__, get_build_hash
from authentik.lib.config import CONFIG
from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import Outpost, OutpostServiceConnection
from authentik.outposts.models import (
Outpost,
OutpostServiceConnection,
OutpostServiceConnectionState,
)
FIELD_MANAGER = "goauthentik.io"
@ -28,11 +31,25 @@ class DeploymentPort:
inner_port: Optional[int] = None
class BaseClient:
"""Base class for custom clients"""
def fetch_state(self) -> OutpostServiceConnectionState:
"""Get state, version info"""
raise NotImplementedError
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Cleanup after usage"""
class BaseController:
"""Base Outpost deployment controller"""
deployment_ports: list[DeploymentPort]
client: BaseClient
outpost: Outpost
connection: OutpostServiceConnection
@ -63,6 +80,14 @@ class BaseController:
self.down()
return [x["event"] for x in logs]
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Cleanup after usage"""
if hasattr(self, "client"):
self.client.__exit__(exc_type, exc_value, traceback)
def get_static_deployment(self) -> str:
"""Return a static deployment configuration"""
raise NotImplementedError
@ -76,5 +101,5 @@ class BaseController:
return image_name_template % {
"type": self.outpost.type,
"version": __version__,
"build_hash": environ.get(ENV_GIT_HASH_KEY, ""),
"build_hash": get_build_hash(),
}

View File

@ -1,17 +1,79 @@
"""Docker controller"""
from time import sleep
from typing import Optional
from urllib.parse import urlparse
from django.conf import settings
from django.utils.text import slugify
from docker import DockerClient
from docker import DockerClient as UpstreamDockerClient
from docker.errors import DockerException, NotFound
from docker.models.containers import Container
from docker.utils.utils import kwargs_from_env
from paramiko.ssh_exception import SSHException
from structlog.stdlib import get_logger
from yaml import safe_dump
from authentik import __version__
from authentik.outposts.controllers.base import BaseController, ControllerException
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.docker_ssh import DockerInlineSSH
from authentik.outposts.docker_tls import DockerInlineTLS
from authentik.outposts.managed import MANAGED_OUTPOST
from authentik.outposts.models import DockerServiceConnection, Outpost, ServiceConnectionInvalid
from authentik.outposts.models import (
DockerServiceConnection,
Outpost,
OutpostServiceConnectionState,
ServiceConnectionInvalid,
)
class DockerClient(UpstreamDockerClient, BaseClient):
"""Custom docker client, which can handle TLS and SSH from a database."""
tls: Optional[DockerInlineTLS]
ssh: Optional[DockerInlineSSH]
def __init__(self, connection: DockerServiceConnection):
self.tls = None
self.ssh = None
if connection.local:
# Same result as DockerClient.from_env
super().__init__(**kwargs_from_env())
else:
parsed_url = urlparse(connection.url)
tls_config = False
if parsed_url.scheme == "ssh":
self.ssh = DockerInlineSSH(parsed_url.hostname, connection.tls_authentication)
self.ssh.write()
else:
self.tls = DockerInlineTLS(
verification_kp=connection.tls_verification,
authentication_kp=connection.tls_authentication,
)
tls_config = self.tls.write()
try:
super().__init__(
base_url=connection.url,
tls=tls_config,
)
except SSHException as exc:
raise ServiceConnectionInvalid from exc
self.logger = get_logger()
# Ensure the client actually works
self.containers.list()
def fetch_state(self) -> OutpostServiceConnectionState:
try:
return OutpostServiceConnectionState(version=self.info()["ServerVersion"], healthy=True)
except (ServiceConnectionInvalid, DockerException):
return OutpostServiceConnectionState(version="", healthy=False)
def __exit__(self, exc_type, exc_value, traceback):
if self.tls:
self.logger.debug("Cleaning up TLS")
self.tls.cleanup()
if self.ssh:
self.logger.debug("Cleaning up SSH")
self.ssh.cleanup()
class DockerController(BaseController):
@ -27,8 +89,9 @@ class DockerController(BaseController):
if outpost.managed == MANAGED_OUTPOST:
return
try:
self.client = connection.client()
except ServiceConnectionInvalid as exc:
self.client = DockerClient(connection)
except DockerException as exc:
self.logger.warning(exc)
raise ControllerException from exc
@property
@ -43,9 +106,12 @@ class DockerController(BaseController):
).lower()
def _get_labels(self) -> dict[str, str]:
return {
labels = {
"io.goauthentik.outpost-uuid": self.outpost.pk.hex,
}
if self.outpost.config.docker_labels:
labels.update(self.outpost.config.docker_labels)
return labels
def _get_env(self) -> dict[str, str]:
return {
@ -110,7 +176,7 @@ class DockerController(BaseController):
image = self.get_container_image()
try:
self.client.images.pull(image)
except DockerException:
except DockerException: # pragma: no cover
image = f"goauthentik.io/{self.outpost.type}:latest"
self.client.images.pull(image)
return image
@ -144,7 +210,7 @@ class DockerController(BaseController):
True,
)
def _migrate_container_name(self):
def _migrate_container_name(self): # pragma: no cover
"""Migrate 2021.9 to 2021.10+"""
old_name = f"authentik-proxy-{self.outpost.uuid.hex}"
try:
@ -169,7 +235,7 @@ class DockerController(BaseController):
# Check if the container is out of date, delete it and retry
if len(container.image.tags) > 0:
should_image = self.try_pull_image()
if should_image not in container.image.tags:
if should_image not in container.image.tags: # pragma: no cover
self.logger.info(
"Container has mismatched image, re-creating...",
has=container.image.tags,

View File

@ -20,6 +20,11 @@ if TYPE_CHECKING:
T = TypeVar("T", V1Pod, V1Deployment)
def get_version() -> str:
"""Wrapper for __version__ to make testing easier"""
return __version__
class KubernetesObjectReconciler(Generic[T]):
"""Base Kubernetes Reconciler, handles the basic logic."""
@ -146,13 +151,13 @@ class KubernetesObjectReconciler(Generic[T]):
return V1ObjectMeta(
namespace=self.namespace,
labels={
"app.kubernetes.io/name": f"authentik-{self.controller.outpost.type.lower()}",
"app.kubernetes.io/instance": slugify(self.controller.outpost.name),
"app.kubernetes.io/version": __version__,
"app.kubernetes.io/managed-by": "goauthentik.io",
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
"app.kubernetes.io/name": f"authentik-{self.controller.outpost.type.lower()}",
"app.kubernetes.io/version": get_version(),
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
},
**kwargs,
)

View File

@ -18,6 +18,7 @@ from kubernetes.client import (
V1SecretKeySelector,
)
from authentik import __version__, get_full_version
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
@ -52,15 +53,18 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
raise NeedsUpdate()
super().reconcile(current, reference)
def get_pod_meta(self) -> dict[str, str]:
def get_pod_meta(self, **kwargs) -> dict[str, str]:
"""Get common object metadata"""
return {
"app.kubernetes.io/name": "authentik-outpost",
"app.kubernetes.io/managed-by": "goauthentik.io",
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
}
kwargs.update(
{
"app.kubernetes.io/name": f"authentik-outpost-{self.outpost.type}",
"app.kubernetes.io/managed-by": "goauthentik.io",
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
}
)
return kwargs
def get_reference_object(self) -> V1Deployment:
"""Get deployment object for outpost"""
@ -77,13 +81,24 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
meta = self.get_object_meta(name=self.name)
image_name = self.controller.get_container_image()
image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets
version = get_full_version()
return V1Deployment(
metadata=meta,
spec=V1DeploymentSpec(
replicas=self.outpost.config.kubernetes_replicas,
selector=V1LabelSelector(match_labels=self.get_pod_meta()),
template=V1PodTemplateSpec(
metadata=V1ObjectMeta(labels=self.get_pod_meta()),
metadata=V1ObjectMeta(
labels=self.get_pod_meta(
**{
# Support istio-specific labels, but also use the standard k8s
# recommendations
"app.kubernetes.io/version": version,
"app": "authentik-outpost",
"version": version,
}
)
),
spec=V1PodSpec(
image_pull_secrets=[
V1ObjectReference(name=secret) for secret in image_pull_secrets

View File

@ -6,6 +6,7 @@ from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
from authentik.outposts.controllers.k8s.utils import compare_ports
if TYPE_CHECKING:
@ -25,6 +26,8 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
# after an authentik update. However the ports might have also changed during
# the update, so this causes the service to be re-created with higher
# priority than being updated.
if current.spec.selector != reference.spec.selector:
raise NeedsUpdate()
super().reconcile(current, reference)
def get_reference_object(self) -> V1Service:

View File

@ -2,6 +2,7 @@
from pathlib import Path
from kubernetes.client.models.v1_container_port import V1ContainerPort
from kubernetes.client.models.v1_service_port import V1ServicePort
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate
@ -16,10 +17,31 @@ def get_namespace() -> str:
return "default"
def compare_ports(current: list[V1ContainerPort], reference: list[V1ContainerPort]):
def compare_port(
current: V1ServicePort | V1ContainerPort, reference: V1ServicePort | V1ContainerPort
) -> bool:
"""Compare a single port"""
if current.name != reference.name:
return False
if current.protocol != reference.protocol:
return False
if isinstance(current, V1ServicePort) and isinstance(reference, V1ServicePort):
# We only care about the target port
if current.target_port != reference.target_port:
return False
if isinstance(current, V1ContainerPort) and isinstance(reference, V1ContainerPort):
# We only care about the target port
if current.container_port != reference.container_port:
return False
return True
def compare_ports(
current: list[V1ServicePort | V1ContainerPort], reference: list[V1ServicePort | V1ContainerPort]
):
"""Compare ports of a list"""
if len(current) != len(reference):
raise NeedsRecreate()
for port in reference:
if port not in current:
if not any(compare_port(port, current_port) for current_port in current):
raise NeedsRecreate()

View File

@ -1,34 +1,67 @@
"""Kubernetes deployment controller"""
from io import StringIO
from typing import Type
from kubernetes.client import VersionApi, VersionInfo
from kubernetes.client.api_client import ApiClient
from kubernetes.client.configuration import Configuration
from kubernetes.client.exceptions import OpenApiException
from kubernetes.config.config_exception import ConfigException
from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict
from structlog.testing import capture_logs
from urllib3.exceptions import HTTPError
from yaml import dump_all
from authentik.outposts.controllers.base import BaseController, ControllerException
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
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.controllers.k8s.service_monitor import PrometheusServiceMonitorReconciler
from authentik.outposts.models import KubernetesServiceConnection, Outpost, ServiceConnectionInvalid
from authentik.outposts.models import (
KubernetesServiceConnection,
Outpost,
OutpostServiceConnectionState,
ServiceConnectionInvalid,
)
class KubernetesClient(ApiClient, BaseClient):
"""Custom kubernetes client based on service connection"""
def __init__(self, connection: KubernetesServiceConnection):
config = Configuration()
try:
if connection.local:
load_incluster_config(client_configuration=config)
else:
load_kube_config_from_dict(connection.kubeconfig, client_configuration=config)
super().__init__(config)
except ConfigException as exc:
raise ServiceConnectionInvalid from exc
def fetch_state(self) -> OutpostServiceConnectionState:
"""Get version info"""
try:
api_instance = VersionApi(self)
version: VersionInfo = api_instance.get_code()
return OutpostServiceConnectionState(version=version.git_version, healthy=True)
except (OpenApiException, HTTPError, ServiceConnectionInvalid):
return OutpostServiceConnectionState(version="", healthy=False)
class KubernetesController(BaseController):
"""Manage deployment of outpost in kubernetes"""
reconcilers: dict[str, Type[KubernetesObjectReconciler]]
reconcilers: dict[str, type[KubernetesObjectReconciler]]
reconcile_order: list[str]
client: ApiClient
client: KubernetesClient
connection: KubernetesServiceConnection
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection) -> None:
super().__init__(outpost, connection)
self.client = connection.client()
self.client = KubernetesClient(connection)
self.reconcilers = {
"secret": SecretReconciler,
"deployment": DeploymentReconciler,

View File

@ -0,0 +1,82 @@
"""Docker SSH helper"""
import os
from pathlib import Path
from tempfile import gettempdir
from authentik.crypto.models import CertificateKeyPair
HEADER = "### Managed by authentik"
FOOTER = "### End Managed by authentik"
def opener(path, flags):
"""File opener to create files as 700 perms"""
return os.open(path, flags, 0o700)
class DockerInlineSSH:
"""Create paramiko ssh config from CertificateKeyPair"""
host: str
keypair: CertificateKeyPair
key_path: str
config_path: Path
header: str
def __init__(self, host: str, keypair: CertificateKeyPair) -> None:
self.host = host
self.keypair = keypair
self.config_path = Path("~/.ssh/config").expanduser()
self.header = f"{HEADER} - {self.host}\n"
def write_config(self, key_path: str) -> bool:
"""Update the local user's ssh config file"""
with open(self.config_path, "a+", encoding="utf-8") as ssh_config:
if self.header in ssh_config.readlines():
return False
ssh_config.writelines(
[
self.header,
f"Host {self.host}\n",
f" IdentityFile {key_path}\n",
f"{FOOTER}\n",
"\n",
]
)
return True
def write_key(self):
"""Write keypair's private key to a temporary file"""
path = Path(gettempdir(), f"{self.keypair.pk}_private.pem")
with open(path, "w", encoding="utf8", opener=opener) as _file:
_file.write(self.keypair.key_data)
return str(path)
def write(self):
"""Write keyfile and update ssh config"""
self.key_path = self.write_key()
was_written = self.write_config(self.key_path)
if not was_written:
self.cleanup()
def cleanup(self):
"""Cleanup when we're done"""
try:
os.unlink(self.key_path)
with open(self.config_path, "r+", encoding="utf-8") as ssh_config:
start = 0
end = 0
lines = ssh_config.readlines()
for idx, line in enumerate(lines):
if line == self.header:
start = idx
if start != 0 and line == f"{FOOTER}\n":
end = idx
with open(self.config_path, "w+", encoding="utf-8") as ssh_config:
lines = lines[:start] + lines[end + 2 :]
ssh_config.writelines(lines)
except OSError:
# If we fail deleting a file it doesn't matter that much
# since we're just in a container
pass

View File

@ -1,4 +1,5 @@
"""Create Docker TLSConfig from CertificateKeyPair"""
from os import unlink
from pathlib import Path
from tempfile import gettempdir
from typing import Optional
@ -14,6 +15,8 @@ class DockerInlineTLS:
verification_kp: Optional[CertificateKeyPair]
authentication_kp: Optional[CertificateKeyPair]
_paths: list[str]
def __init__(
self,
verification_kp: Optional[CertificateKeyPair],
@ -21,14 +24,21 @@ class DockerInlineTLS:
) -> None:
self.verification_kp = verification_kp
self.authentication_kp = authentication_kp
self._paths = []
def write_file(self, name: str, contents: str) -> str:
"""Wrapper for mkstemp that uses fdopen"""
path = Path(gettempdir(), name)
with open(path, "w", encoding="utf8") as _file:
_file.write(contents)
self._paths.append(str(path))
return str(path)
def cleanup(self):
"""Clean up certificates when we're done"""
for path in self._paths:
unlink(path)
def write(self) -> TLSConfig:
"""Create TLSConfig with Certificate Key pairs"""
# So yes, this is quite ugly. But sadly, there is no clean way to pass

View File

@ -1,8 +1,7 @@
"""Outpost models"""
from dataclasses import asdict, dataclass, field
from datetime import datetime
from os import environ
from typing import Iterable, Optional, Union
from typing import Iterable, Optional
from uuid import uuid4
from dacite import from_dict
@ -11,23 +10,13 @@ from django.core.cache import cache
from django.db import IntegrityError, models, transaction
from django.db.models.base import Model
from django.utils.translation import gettext_lazy as _
from docker.client import DockerClient
from docker.errors import DockerException
from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from kubernetes.client import VersionApi, VersionInfo
from kubernetes.client.api_client import ApiClient
from kubernetes.client.configuration import Configuration
from kubernetes.client.exceptions import OpenApiException
from kubernetes.config.config_exception import ConfigException
from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict
from model_utils.managers import InheritanceManager
from packaging.version import LegacyVersion, Version, parse
from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik import __version__, get_build_hash
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
USER_ATTRIBUTE_SA,
@ -44,7 +33,7 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.managed.models import ManagedModel
from authentik.outposts.controllers.k8s.utils import get_namespace
from authentik.outposts.docker_tls import DockerInlineTLS
from authentik.tenants.models import Tenant
OUR_VERSION = parse(__version__)
OUTPOST_HELLO_INTERVAL = 10
@ -71,6 +60,7 @@ class OutpostConfig:
docker_network: Optional[str] = field(default=None)
docker_map_ports: bool = field(default=True)
docker_labels: Optional[dict[str, str]] = field(default=None)
container_image: Optional[str] = field(default=None)
@ -86,7 +76,7 @@ class OutpostConfig:
class OutpostModel(Model):
"""Base model for providers that need more objects than just themselves"""
def get_required_objects(self) -> Iterable[Union[models.Model, str]]:
def get_required_objects(self) -> Iterable[models.Model | str]:
"""Return a list of all required objects"""
return [self]
@ -149,10 +139,6 @@ class OutpostServiceConnection(models.Model):
return OutpostServiceConnectionState("", False)
return state
def fetch_state(self) -> OutpostServiceConnectionState:
"""Fetch current Service Connection state"""
raise NotImplementedError
@property
def component(self) -> str:
"""Return component used to edit this object"""
@ -210,35 +196,6 @@ class DockerServiceConnection(OutpostServiceConnection):
def __str__(self) -> str:
return f"Docker Service-Connection {self.name}"
def client(self) -> DockerClient:
"""Get DockerClient"""
try:
client = None
if self.local:
client = DockerClient.from_env()
else:
client = DockerClient(
base_url=self.url,
tls=DockerInlineTLS(
verification_kp=self.tls_verification,
authentication_kp=self.tls_authentication,
).write(),
)
client.containers.list()
except DockerException as exc:
LOGGER.warning(exc)
raise ServiceConnectionInvalid from exc
return client
def fetch_state(self) -> OutpostServiceConnectionState:
try:
client = self.client()
return OutpostServiceConnectionState(
version=client.info()["ServerVersion"], healthy=True
)
except ServiceConnectionInvalid:
return OutpostServiceConnectionState(version="", healthy=False)
class Meta:
verbose_name = _("Docker Service-Connection")
@ -265,27 +222,6 @@ class KubernetesServiceConnection(OutpostServiceConnection):
def __str__(self) -> str:
return f"Kubernetes Service-Connection {self.name}"
def fetch_state(self) -> OutpostServiceConnectionState:
try:
client = self.client()
api_instance = VersionApi(client)
version: VersionInfo = api_instance.get_code()
return OutpostServiceConnectionState(version=version.git_version, healthy=True)
except (OpenApiException, HTTPError, ServiceConnectionInvalid):
return OutpostServiceConnectionState(version="", healthy=False)
def client(self) -> ApiClient:
"""Get Kubernetes client configured from kubeconfig"""
config = Configuration()
try:
if self.local:
load_incluster_config(client_configuration=config)
else:
load_kube_config_from_dict(self.kubeconfig, client_configuration=config)
return ApiClient(config)
except ConfigException as exc:
raise ServiceConnectionInvalid from exc
class Meta:
verbose_name = _("Kubernetes Service-Connection")
@ -385,7 +321,8 @@ class Outpost(ManagedModel):
user.user_permissions.add(permission.first())
LOGGER.debug(
"Updated service account's permissions",
perms=UserObjectPermission.objects.filter(user=user),
obj_perms=UserObjectPermission.objects.filter(user=user),
perms=user.user_permissions.all(),
)
@property
@ -438,9 +375,9 @@ class Outpost(ManagedModel):
Token.objects.filter(identifier=self.token_identifier).delete()
return self.token
def get_required_objects(self) -> Iterable[Union[models.Model, str]]:
def get_required_objects(self) -> Iterable[models.Model | str]:
"""Get an iterator of all objects the user needs read access to"""
objects: list[Union[models.Model, str]] = [
objects: list[models.Model | str] = [
self,
"authentik_events.add_event",
]
@ -449,6 +386,10 @@ class Outpost(ManagedModel):
objects.extend(provider.get_required_objects())
else:
objects.append(provider)
if self.managed:
for tenant in Tenant.objects.filter(web_certificate__isnull=False):
objects.append(tenant)
objects.append(tenant.web_certificate)
return objects
def __str__(self) -> str:
@ -463,7 +404,7 @@ class OutpostState:
channel_ids: list[str] = field(default_factory=list)
last_seen: Optional[datetime] = field(default=None)
version: Optional[str] = field(default=None)
version_should: Union[Version, LegacyVersion] = field(default=OUR_VERSION)
version_should: Version | LegacyVersion = field(default=OUR_VERSION)
build_hash: str = field(default="")
_outpost: Optional[Outpost] = field(default=None)
@ -473,7 +414,7 @@ class OutpostState:
"""Check if outpost version matches our version"""
if not self.version:
return False
if self.build_hash != environ.get(ENV_GIT_HASH_KEY, ""):
if self.build_hash != get_build_hash():
return False
return parse(self.version) < OUR_VERSION
@ -481,6 +422,8 @@ class OutpostState:
def for_outpost(outpost: Outpost) -> list["OutpostState"]:
"""Get all states for an outpost"""
keys = cache.keys(f"{outpost.state_cache_prefix}_*")
if not keys:
return []
states = []
for key in keys:
instance_uid = key.replace(f"{outpost.state_cache_prefix}_", "")

View File

@ -10,6 +10,7 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.lib.utils.reflection import class_to_path
from authentik.outposts.models import Outpost, OutpostServiceConnection
from authentik.outposts.tasks import CACHE_KEY_OUTPOST_DOWN, outpost_controller, outpost_post_save
from authentik.tenants.models import Tenant
LOGGER = get_logger()
UPDATE_TRIGGERING_MODELS = (
@ -17,6 +18,7 @@ UPDATE_TRIGGERING_MODELS = (
OutpostServiceConnection,
Provider,
CertificateKeyPair,
Tenant,
)

View File

@ -25,6 +25,8 @@ from authentik.events.monitored_tasks import (
)
from authentik.lib.utils.reflection import path_to_class
from authentik.outposts.controllers.base import BaseController, ControllerException
from authentik.outposts.controllers.docker import DockerClient
from authentik.outposts.controllers.kubernetes import KubernetesClient
from authentik.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
@ -45,21 +47,21 @@ LOGGER = get_logger()
CACHE_KEY_OUTPOST_DOWN = "outpost_teardown_%s"
def controller_for_outpost(outpost: Outpost) -> Optional[BaseController]:
def controller_for_outpost(outpost: Outpost) -> Optional[type[BaseController]]:
"""Get a controller for the outpost, when a service connection is defined"""
if not outpost.service_connection:
return None
service_connection = outpost.service_connection
if outpost.type == OutpostType.PROXY:
if isinstance(service_connection, DockerServiceConnection):
return ProxyDockerController(outpost, service_connection)
return ProxyDockerController
if isinstance(service_connection, KubernetesServiceConnection):
return ProxyKubernetesController(outpost, service_connection)
return ProxyKubernetesController
if outpost.type == OutpostType.LDAP:
if isinstance(service_connection, DockerServiceConnection):
return LDAPDockerController(outpost, service_connection)
return LDAPDockerController
if isinstance(service_connection, KubernetesServiceConnection):
return LDAPKubernetesController(outpost, service_connection)
return LDAPKubernetesController
return None
@ -71,7 +73,16 @@ def outpost_service_connection_state(connection_pk: Any):
)
if not connection:
return
state = connection.fetch_state()
if isinstance(connection, DockerServiceConnection):
cls = DockerClient
if isinstance(connection, KubernetesServiceConnection):
cls = KubernetesClient
try:
with cls(connection) as client:
state = client.fetch_state()
except ServiceConnectionInvalid as exc:
LOGGER.warning("Failed to get client status", exc=exc)
return
cache.set(connection.state_key, state, timeout=None)
@ -114,14 +125,15 @@ def outpost_controller(
return
self.set_uid(slugify(outpost.name))
try:
controller = controller_for_outpost(outpost)
if not controller:
controller_type = controller_for_outpost(outpost)
if not controller_type:
return
logs = getattr(controller, f"{action}_with_logs")()
LOGGER.debug("---------------Outpost Controller logs starting----------------")
for log in logs:
LOGGER.debug(log)
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
with controller_type(outpost, outpost.service_connection) as controller:
logs = getattr(controller, f"{action}_with_logs")()
LOGGER.debug("---------------Outpost Controller logs starting----------------")
for log in logs:
LOGGER.debug(log)
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
else:

View File

@ -0,0 +1,124 @@
"""Docker controller tests"""
from django.test import TestCase
from docker.models.containers import Container
from authentik.managed.manager import ObjectManager
from authentik.outposts.controllers.base import ControllerException
from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.managed import MANAGED_OUTPOST
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
from authentik.providers.proxy.controllers.docker import ProxyDockerController
class DockerControllerTests(TestCase):
"""Docker controller tests"""
def setUp(self) -> None:
self.outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
)
self.integration = DockerServiceConnection(name="test")
ObjectManager().run()
def test_init_managed(self):
"""Docker controller shouldn't do anything for managed outpost"""
controller = DockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
self.assertIsNone(controller.up())
self.assertIsNone(controller.down())
def test_init_invalid(self):
"""Ensure init fails with invalid client"""
with self.assertRaises(ControllerException):
DockerController(self.outpost, self.integration)
def test_env_valid(self):
"""Test environment check"""
controller = DockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
env = [f"{key}={value}" for key, value in controller._get_env().items()]
container = Container(attrs={"Config": {"Env": env}})
self.assertFalse(controller._comp_env(container))
def test_env_invalid(self):
"""Test environment check"""
controller = DockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
container = Container(attrs={"Config": {"Env": []}})
self.assertTrue(controller._comp_env(container))
def test_label_valid(self):
"""Test label check"""
controller = DockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
container = Container(attrs={"Config": {"Labels": controller._get_labels()}})
self.assertFalse(controller._comp_labels(container))
def test_label_invalid(self):
"""Test label check"""
controller = DockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
container = Container(attrs={"Config": {"Labels": {}}})
self.assertTrue(controller._comp_labels(container))
container = Container(attrs={"Config": {"Labels": {"io.goauthentik.outpost-uuid": "foo"}}})
self.assertTrue(controller._comp_labels(container))
def test_port_valid(self):
"""Test port check"""
controller = ProxyDockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
container = Container(
attrs={
"NetworkSettings": {
"Ports": {
"9000/tcp": [{"HostIp": "", "HostPort": "9000"}],
"9443/tcp": [{"HostIp": "", "HostPort": "9443"}],
}
},
"State": "",
}
)
with self.settings(TEST=False):
self.assertFalse(controller._comp_ports(container))
container.attrs["State"] = "running"
self.assertFalse(controller._comp_ports(container))
def test_port_invalid(self):
"""Test port check"""
controller = ProxyDockerController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(), self.integration
)
container_no_ports = Container(
attrs={"NetworkSettings": {"Ports": None}, "State": "running"}
)
container_missing_port = Container(
attrs={
"NetworkSettings": {
"Ports": {
"9443/tcp": [{"HostIp": "", "HostPort": "9443"}],
}
},
"State": "running",
}
)
container_mismatched_host = Container(
attrs={
"NetworkSettings": {
"Ports": {
"9443/tcp": [{"HostIp": "", "HostPort": "123"}],
}
},
"State": "running",
}
)
with self.settings(TEST=False):
self.assertFalse(controller._comp_ports(container_no_ports))
self.assertTrue(controller._comp_ports(container_missing_port))
self.assertTrue(controller._comp_ports(container_mismatched_host))

View File

@ -5,7 +5,7 @@ from typing import Iterator, Optional
from django.core.cache import cache
from django.http import HttpRequest
from prometheus_client import Histogram
from prometheus_client import Gauge, Histogram
from sentry_sdk.hub import Hub
from sentry_sdk.tracing import Span
from structlog.stdlib import BoundLogger, get_logger
@ -14,13 +14,11 @@ from authentik.core.models import User
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel, PolicyEngineMode
from authentik.policies.process import PolicyProcess, cache_key
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.root.monitoring import UpdatingGauge
CURRENT_PROCESS = current_process()
GAUGE_POLICIES_CACHED = UpdatingGauge(
GAUGE_POLICIES_CACHED = Gauge(
"authentik_policies_cached",
"Cached Policies",
update_func=lambda: len(cache.keys("policy_*") or []),
)
HIST_POLICIES_BUILD_TIME = Histogram(
"authentik_policies_build_time",

View File

@ -45,7 +45,7 @@ class HaveIBeenPwendPolicy(Policy):
fields=request.context.keys(),
)
return PolicyResult(False, _("Password not set in context"))
password = request.context[self.password_field]
password = str(request.context[self.password_field])
pw_hash = sha1(password.encode("utf-8")).hexdigest() # nosec
url = f"https://api.pwnedpasswords.com/range/{pw_hash[:5]}"

View File

@ -13,6 +13,7 @@ class PasswordPolicySerializer(PolicySerializer):
model = PasswordPolicy
fields = PolicySerializer.Meta.fields + [
"password_field",
"amount_digits",
"amount_uppercase",
"amount_lowercase",
"amount_symbols",

View File

@ -0,0 +1,38 @@
# Generated by Django 4.0 on 2021-12-18 14:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_password", "0002_passwordpolicy_password_field"),
]
operations = [
migrations.AddField(
model_name="passwordpolicy",
name="amount_digits",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_lowercase",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_symbols",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_uppercase",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="length_min",
field=models.PositiveIntegerField(default=0),
),
]

View File

@ -13,6 +13,7 @@ from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
RE_LOWER = re.compile("[a-z]")
RE_UPPER = re.compile("[A-Z]")
RE_DIGITS = re.compile("[0-9]")
class PasswordPolicy(Policy):
@ -23,10 +24,11 @@ class PasswordPolicy(Policy):
help_text=_("Field key to check, field keys defined in Prompt stages are available."),
)
amount_uppercase = models.IntegerField(default=0)
amount_lowercase = models.IntegerField(default=0)
amount_symbols = models.IntegerField(default=0)
length_min = models.IntegerField(default=0)
amount_digits = models.PositiveIntegerField(default=0)
amount_uppercase = models.PositiveIntegerField(default=0)
amount_lowercase = models.PositiveIntegerField(default=0)
amount_symbols = models.PositiveIntegerField(default=0)
length_min = models.PositiveIntegerField(default=0)
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
error_message = models.TextField()
@ -40,6 +42,7 @@ class PasswordPolicy(Policy):
def component(self) -> str:
return "ak-policy-password-form"
# pylint: disable=too-many-return-statements
def passes(self, request: PolicyRequest) -> PolicyResult:
if (
self.password_field not in request.context
@ -62,6 +65,9 @@ class PasswordPolicy(Policy):
LOGGER.debug("password failed", reason="length")
return PolicyResult(False, self.error_message)
if self.amount_digits > 0 and len(RE_DIGITS.findall(password)) < self.amount_digits:
LOGGER.debug("password failed", reason="amount_digits")
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)

View File

@ -1,16 +1,14 @@
"""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.flows.tests import FlowTestCase
from authentik.policies.password.models import PasswordPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
class TestPasswordPolicyFlow(APITestCase):
class TestPasswordPolicyFlow(FlowTestCase):
"""Test Password Policy"""
def setUp(self) -> None:
@ -53,29 +51,22 @@ class TestPasswordPolicyFlow(APITestCase):
{"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",
"sub_text": "",
}
],
"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,
self.assertStageResponse(
response,
self.flow,
component="ak-stage-prompt",
fields=[
{
"field_key": "password",
"label": "PASSWORD_LABEL",
"order": 0,
"placeholder": "PASSWORD_PLACEHOLDER",
"required": True,
"type": "password",
"sub_text": "",
}
],
response_errors={
"non_field_errors": [{"code": "invalid", "string": self.policy.error_message}]
},
)

View File

@ -13,6 +13,7 @@ class TestPasswordPolicy(TestCase):
def setUp(self) -> None:
self.policy = PasswordPolicy.objects.create(
name="test_false",
amount_digits=1,
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
@ -38,7 +39,7 @@ class TestPasswordPolicy(TestCase):
def test_failed_lowercase(self):
"""not enough lowercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe" # nosec
request.context["password"] = "1TTTTTTTTTTTTTTTTTTTTTTe" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
@ -46,15 +47,23 @@ class TestPasswordPolicy(TestCase):
def test_failed_uppercase(self):
"""not enough uppercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "tttttttttttttttttttttttE" # nosec
request.context["password"] = "1tttttttttttttttttttttE" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_symbols(self):
"""not enough uppercase"""
"""not enough symbols"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TETETETETETETETETETETETETe!!!" # nosec
request.context["password"] = "1ETETETETETETETETETETETETe!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_digits(self):
"""not enough digits"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TETETETETETETETETETETE1e!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
@ -62,7 +71,7 @@ class TestPasswordPolicy(TestCase):
def test_true(self):
"""Positive password case"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = generate_key() + "ee!!!" # nosec
request.context["password"] = generate_key() + "1ee!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -1,11 +1,11 @@
"""Source API Views"""
"""Reputation policy API Views"""
from rest_framework import mixins
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.policies.api.policies import PolicySerializer
from authentik.policies.reputation.models import IPReputation, ReputationPolicy, UserReputation
from authentik.policies.reputation.models import Reputation, ReputationPolicy
class ReputationPolicySerializer(PolicySerializer):
@ -29,59 +29,32 @@ class ReputationPolicyViewSet(UsedByMixin, ModelViewSet):
ordering = ["name"]
class IPReputationSerializer(ModelSerializer):
"""IPReputation Serializer"""
class ReputationSerializer(ModelSerializer):
"""Reputation Serializer"""
class Meta:
model = IPReputation
model = Reputation
fields = [
"pk",
"identifier",
"ip",
"ip_geo_data",
"score",
"updated",
]
class IPReputationViewSet(
class ReputationViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""IPReputation Viewset"""
"""Reputation Viewset"""
queryset = IPReputation.objects.all()
serializer_class = IPReputationSerializer
search_fields = ["ip", "score"]
filterset_fields = ["ip", "score"]
queryset = Reputation.objects.all()
serializer_class = ReputationSerializer
search_fields = ["identifier", "ip", "score"]
filterset_fields = ["identifier", "ip", "score"]
ordering = ["ip"]
class UserReputationSerializer(ModelSerializer):
"""UserReputation Serializer"""
class Meta:
model = UserReputation
fields = [
"pk",
"username",
"score",
"updated",
]
class UserReputationViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""UserReputation Viewset"""
queryset = UserReputation.objects.all()
serializer_class = UserReputationSerializer
search_fields = ["username", "score"]
filterset_fields = ["username", "score"]
ordering = ["username"]

View File

@ -13,3 +13,4 @@ class AuthentikPolicyReputationConfig(AppConfig):
def ready(self):
import_module("authentik.policies.reputation.signals")
import_module("authentik.policies.reputation.tasks")

View File

@ -0,0 +1,40 @@
# Generated by Django 4.0.1 on 2022-01-05 18:56
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_reputation", "0002_auto_20210529_2046"),
]
operations = [
migrations.CreateModel(
name="Reputation",
fields=[
(
"reputation_uuid",
models.UUIDField(
default=uuid.uuid4, primary_key=True, serialize=False, unique=True
),
),
("identifier", models.TextField()),
("ip", models.GenericIPAddressField()),
("ip_geo_data", models.JSONField(default=dict)),
("score", models.BigIntegerField(default=0)),
("updated", models.DateTimeField(auto_now_add=True)),
],
options={
"unique_together": {("identifier", "ip")},
},
),
migrations.DeleteModel(
name="IPReputation",
),
migrations.DeleteModel(
name="UserReputation",
),
]

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