Compare commits

..

113 Commits

Author SHA1 Message Date
897f6f3473 release: 2021.8.1 2021-08-26 16:03:45 +02:00
b70b44490b root: Require PG_PASS to be set (#1303)
This raises an error when PG_PASS is not set.

docker-compose recently changed the way .env files are searched for (see
for example https://github.com/docker/compose/issues/8347) and with the
current setup, authentik will not work anyway without a password set.
2021-08-26 10:24:35 +02:00
77a5a58cb9 root: Fix table of contents for CONTRIBUTING.md (#1302) 2021-08-26 10:08:07 +02:00
f3b227434e web: Update Web API Client version (#1301)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-26 10:06:46 +02:00
2ae164df78 *: cleanup api schema warnings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-26 09:36:41 +02:00
9b09793230 build(deps): bump drf-spectacular from 0.17.3 to 0.18.0 (#1299) 2021-08-26 08:54:59 +02:00
f8a401aeca build(deps): bump boto3 from 1.18.28 to 1.18.29 (#1300) 2021-08-26 08:53:56 +02:00
ffbab2cd68 outpost/ldap: set request_id in sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 22:36:08 +02:00
734e5fcab4 web: Update Web API Client version (#1298)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-25 21:52:32 +02:00
78578c6c9d web/admin: allow admins to create tokens
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 21:23:32 +02:00
0ccec96490 core: make user optional in token creation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 21:21:51 +02:00
8022d0801d web/elements: add support for datetime-local
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 21:14:32 +02:00
d79975c409 core: fix user object for token not be setable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 20:43:34 +02:00
20d65035d5 core: fix error when user updates themselves
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 17:52:50 +02:00
8d6227377f core: fix error for asgi error handler with websockets
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-25 10:24:01 +02:00
4bc50e7f57 build(deps): bump boto3 from 1.18.27 to 1.18.28 (#1296) 2021-08-25 08:35:12 +02:00
945e42c940 web: Update Web API Client version (#1295) 2021-08-24 22:35:08 +02:00
052bb28086 ci: only generate NPM API package on master
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 21:44:47 +02:00
4a84b7e2d5 web: Update Web API Client version (#1294)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-24 21:32:29 +02:00
4d27694706 release: 2021.8.1-rc2 2021-08-24 21:29:29 +02:00
16cfa8cae2 web/admin: add ServiceAccount creation form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 20:13:08 +02:00
1a20c8ffc1 web: Update Web API Client version (#1293)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-24 20:12:46 +02:00
d7ad5f6a16 core: add API to create service account with token for app password
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 20:09:22 +02:00
5af9a3d3be sources/saml: fix error when getting metadata
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 16:51:08 +02:00
dec34bc948 stages/password: fix replace_inbuilt not being called
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 16:37:39 +02:00
cff37caa57 web: Update Web API Client version (#1292) 2021-08-24 14:32:33 +02:00
cc6d5765f2 web/admin: fix inconsistent ordering for ldap property mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 13:04:19 +02:00
2ec1ff2ebb sources/ldap: fix error when modifying ldap source with password write-back
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 13:03:41 +02:00
884c2bd0e9 root: fix missing ldap backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 13:03:19 +02:00
2c938ec9dc stages/password: sort backends in migration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 12:44:45 +02:00
9733caf3b7 admin: use copy for environ api
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 11:39:49 +02:00
494af0a430 web: Update Web API Client version (#1291)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-24 11:01:51 +02:00
10e50bc77f stages/user_login: improve logging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 10:58:50 +02:00
44bfbb9e49 Merge branch 'master' into next 2021-08-24 10:58:27 +02:00
5be152e12d stages/password: fix migration error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 10:57:20 +02:00
b0efab6d6d admin: add env to API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 10:55:46 +02:00
f2725b88c8 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1289) 2021-08-24 10:33:59 +02:00
24cc123029 build(deps): bump @typescript-eslint/parser in /web (#1288) 2021-08-24 10:16:49 +02:00
d75c9997f6 build(deps): bump boto3 from 1.18.26 to 1.18.27 (#1290) 2021-08-24 10:16:41 +02:00
0a20a30af3 ci: remove zeus.ci :(
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-24 00:07:02 +02:00
c60ba91fee core: fix auth saving entire models into session
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 23:59:43 +02:00
37927c9361 web: Update Web API Client version (#1287)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-23 22:06:25 +02:00
0a63441935 website/docs: update release notes 2021.8
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 21:24:53 +02:00
6b7a8b6ac7 core: add new token intent and auth backend (#1284)
* core: add new token intent and auth backend

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

* root: update schema

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

* web/admin: allow users to create app password tokens

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

* web/admin: display token's intents

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

* stages/password: auto-enable app password backend

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

* web/admin: fix missing app passwords backend

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

* core: use custom inbuilt backend, set backend login information in flow plan for events

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

* website/docs: add docs for `auth_method` and `auth_method_args` fields

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

* website: fix example flows using incorrect backend

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

* root: add alias for akflow files

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

* core: fix token intent not defaulting correctly

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

* website: update akflows orders

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

* web/admin: improve delete modal for stage bindings and policy bindings

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

* events: fix linting

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

* website: make default login-2fa flow ignore 2fa with app passwords

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

* web/admin: select all password stage backends by default

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

* root: fix mis-matched postgres version for CI

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

* web: fix lint error

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

* core: fix authentication error when no request is given

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

* ci: set debug log level

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

* stages/user_write: fix wrong fallback authentication backend

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

* core: add token tests for invalid intent and token auth

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 21:21:39 +02:00
cba255eaaa Merge branch 'master' into app-passwords
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/core/tests/test_source_flow_manager.py
#	authentik/stages/authenticator_validate/tests.py
#	authentik/stages/password/tests.py
#	scripts/generate_ci_config.py
2021-08-23 21:21:12 +02:00
859cf2bd8f lib: move id and key generators to lib (#1286)
* lib: move generators to lib

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

* core: bump default token key size

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

* *: fix split being used for http basic auth instead of partition

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

* web/elements: don't rethrow error in ActionButton

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 20:27:38 +02:00
a2578ffaad core: add token tests for invalid intent and token auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 20:21:54 +02:00
888526a2a7 stages/user_write: fix wrong fallback authentication backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 19:31:23 +02:00
0d00b9cc0d ci: set debug log level
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 19:14:24 +02:00
27cc5d7138 core: fix authentication error when no request is given
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 19:09:53 +02:00
b2f077645a web: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 18:38:35 +02:00
2878597603 root: fix mis-matched postgres version for CI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 18:25:31 +02:00
5face5410f web/admin: select all password stage backends by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 18:08:29 +02:00
1b8750e13b website: make default login-2fa flow ignore 2fa with app passwords
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:50:42 +02:00
e27a6fdeeb events: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:48:28 +02:00
a9af40f85c web/admin: improve delete modal for stage bindings and policy bindings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:46:51 +02:00
59f04963be website: update akflows orders
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:39:19 +02:00
033c9a3bd3 core: fix token intent not defaulting correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:33:35 +02:00
09e3d616e9 root: add alias for akflow files
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:29:12 +02:00
0b280c0a47 website: fix example flows using incorrect backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:26:07 +02:00
07a4f474f4 website/docs: add docs for auth_method and auth_method_args fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:23:55 +02:00
244dc671db Merge branch 'master' into app-passwords 2021-08-23 17:12:17 +02:00
4308136108 root: fix error_handler for websocket
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:12:11 +02:00
69a0153619 core: use custom inbuilt backend, set backend login information in flow plan for events
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 17:09:53 +02:00
2655768f5a Merge branch 'master' into app-passwords 2021-08-23 16:48:43 +02:00
73c55b56a0 ci: update commit message for web api update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:48:28 +02:00
bcbdd6c26f web: Update Web API Client version (#1283)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-08-23 16:47:56 +02:00
00e9b91f56 web/admin: fix missing app passwords backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:47:38 +02:00
4cf76fdcda stages/password: auto-enable app password backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:39:39 +02:00
c4832206fa web/admin: display token's intents
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:33:12 +02:00
d05562a388 Merge branch 'master' into app-passwords 2021-08-23 16:28:25 +02:00
f217d34a98 web/admin: allow users to create app password tokens
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:27:39 +02:00
89f2967f69 ci: only run npm client push on master and version branches
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:21:27 +02:00
9a6a3e66b8 root: update schema
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:14:33 +02:00
2f4b18ebbd web: fix license for API Client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:12:31 +02:00
20572c728d core: add new token intent and auth backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 16:05:29 +02:00
aad753de68 ci: fix extraction of generated client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:57:56 +02:00
a79a150a1f root: test schema auto-update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:55:26 +02:00
8b23e4701a ci: upgrade web api client when schema changes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:54:58 +02:00
a366d61891 root: add License to NPM package
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:41:49 +02:00
9a13dfd63a website/docs: update release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:40:15 +02:00
32d80829e2 web/admin: show system status first
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:25:35 +02:00
f6953296d8 outposts: add recursion limit for docker controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:25:28 +02:00
e4790f9060 core: handle error when ?for_user is not numberical
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:25:18 +02:00
58712047e1 root: add ASGI Error handler
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:15:12 +02:00
85915905dc web/flows: fix error during error handling
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 15:11:30 +02:00
52f2838f57 lifecycle: rename to ak
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 14:54:02 +02:00
12e2f7b945 outposts: add repair_permissions command
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 14:53:53 +02:00
45d47f828a outpost: handle non-existant permission
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 14:39:47 +02:00
cf7eb88661 web: add custom readme to api client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 12:00:35 +02:00
6a14ae7975 web: Merge pull request #1258 from goauthentik/publish-api-to-npm
Publish api to npm
2021-08-23 11:43:25 +02:00
08f3294a1d web: add ESM to generated Client
https://github.com/OpenAPITools/openapi-generator/issues/8881
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 11:11:18 +02:00
ac47fc9295 web: use custom client for web linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:36:32 +02:00
1ff19e1467 web: fix formatting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:33:45 +02:00
439454a71b website: add docs for making schema changes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:32:37 +02:00
2a11964e1a Merge branch 'master' into publish-api-to-npm 2021-08-23 10:16:35 +02:00
507b8d43fb root: remove remainders from gen
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:16:24 +02:00
7efec281be build(deps-dev): bump pylint from 2.9.6 to 2.10.2 (#1280)
* build(deps-dev): bump pylint from 2.9.6 to 2.10.2

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

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

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

* *: add missing encoding to open() calls

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:10:31 +02:00
9469f86f65 web: improve api client versioning
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:09:56 +02:00
e998919097 web: fix build not working
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 10:02:39 +02:00
450d69a1a4 web: build. api in different folder
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-23 09:49:09 +02:00
b74681f22c Merge branch 'master' into publish-api-to-npm
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/package-lock.json
#	web/src/pages/sources/oauth/OAuthSourceForm.ts
2021-08-23 09:40:52 +02:00
f95a7c26e5 build(deps): bump github.com/go-openapi/strfmt from 0.20.1 to 0.20.2 (#1281)
Bumps [github.com/go-openapi/strfmt](https://github.com/go-openapi/strfmt) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/go-openapi/strfmt/releases)
- [Commits](https://github.com/go-openapi/strfmt/compare/v0.20.1...v0.20.2)

---
updated-dependencies:
- dependency-name: github.com/go-openapi/strfmt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-23 07:44:28 +02:00
ffc9bd2cec build(deps): bump boto3 from 1.18.25 to 1.18.26 (#1282) 2021-08-23 07:06:16 +02:00
bb7db0c828 build(deps): bump codemirror from 5.62.2 to 5.62.3 in /web (#1279) 2021-08-23 07:06:04 +02:00
aec3e08201 Merge branch 'version-2021.8' into next 2021-08-22 21:57:14 +02:00
0651fbba06 website/docs: add 2021.8 to sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-22 20:19:23 +02:00
bd9cd086a0 Merge branch 'master' into publish-api-to-npm 2021-08-16 17:29:36 +02:00
14fb0c3d61 web: cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-15 22:18:03 +02:00
c52afe5952 Merge branch 'master' into publish-api-to-npm 2021-08-15 21:40:11 +02:00
1d4b941a3b web: migrate to @goauthentik/api
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-15 21:32:28 +02:00
0344e5d9b3 root: remove usage of make-gen
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-15 21:32:17 +02:00
d8e8cc062b ci: add pipeline to build and push js api package
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-15 21:32:06 +02:00
315 changed files with 4559 additions and 2918 deletions

View File

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

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.8.1-rc1,
beryju/authentik:2021.8.1,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.8.1-rc1,
ghcr.io/goauthentik/server:2021.8.1,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.8.1-rc1,
beryju/authentik-proxy:2021.8.1,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.8.1-rc1,
ghcr.io/goauthentik/proxy:2021.8.1,
ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.8.1-rc1,
beryju/authentik-ldap:2021.8.1,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.8.1-rc1,
ghcr.io/goauthentik/ldap:2021.8.1,
ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.8.1-rc1', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.8.1', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -163,7 +163,6 @@ jobs:
- name: Build web api client and web ui
run: |
export NODE_ENV=production
make gen-web
cd web
npm i
npm run build
@ -176,7 +175,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.8.1-rc1
version: authentik@2021.8.1
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

39
.github/workflows/web-api-publish.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: authentik-web-api-publish
on:
push:
branches: [ master ]
paths:
- 'schema.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2
with:
node-version: '16.x'
registry-url: 'https://registry.npmjs.org'
- name: Generate API Client
run: make gen-web
- name: Publish package
run: |
cd web-api/
npm i
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
- name: Upgrade /web
run: |
cd web/
export VERSION=`node -e 'console.log(require("../web-api/package.json").version)'`
npm i @goauthentik/api@$VERSION
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update-web-api-client
commit-message: "web: Update Web API Client version"
title: "web: Update Web API Client version"
delete-branch: true
signoff: true

1
.gitignore vendored
View File

@ -201,3 +201,4 @@ media/
.idea/
/api/
/web-api/

22
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"cSpell.words": [
"asgi",
"authentik",
"authn",
"goauthentik",
"jwks",
"oidc",
"openid",
"plex",
"saml",
"totp",
"webauthn"
],
"python.linting.pylintEnabled": true,
"todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true,
"python.formatting.provider": "black",
"files.associations": {
"*.akflow": "json"
}
}

View File

@ -11,8 +11,8 @@ The following is a set of guidelines for contributing to authentik and its compo
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Atom and Packages](#atom-and-packages)
* [Atom Design Decisions](#design-decisions)
* [The components](#the-components)
* [authentik's structure](#authentiks-structure)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
@ -22,14 +22,9 @@ The following is a set of guidelines for contributing to authentik and its compo
[Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [JavaScript Styleguide](#javascript-styleguide)
* [CoffeeScript Styleguide](#coffeescript-styleguide)
* [Specs Styleguide](#specs-styleguide)
* [Python Styleguide](#python-styleguide)
* [Documentation Styleguide](#documentation-styleguide)
[Additional Notes](#additional-notes)
* [Issue and Pull Request Labels](#issue-and-pull-request-labels)
## Code of Conduct
Basically, don't be a dickhead. This is an open-source non-profit project, that is made in the free time of Volunteers. If there's something you dislike or think can be done better, tell us! We'd love to hear any suggestions for improvement.

View File

@ -18,17 +18,6 @@ COPY ./website /static/
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-docs-only
# Stage 3: Build web API
FROM openapitools/openapi-generator-cli as web-api-builder
COPY ./schema.yml /local/schema.yml
RUN docker-entrypoint.sh generate \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
# Stage 3: Generate API Client
FROM openapitools/openapi-generator-cli as go-api-builder
@ -48,7 +37,6 @@ RUN docker-entrypoint.sh generate \
FROM node as web-builder
COPY ./web /static/
COPY --from=web-api-builder /local/web/api /static/api
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
@ -110,4 +98,5 @@ COPY --from=builder /work/authentik /authentik-proxy
USER authentik
ENV TMPDIR /dev/shm/
ENV PYTHONUBUFFERED 1
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/lifecycle"
ENTRYPOINT [ "/lifecycle/ak" ]

View File

@ -2,6 +2,7 @@
PWD = $(shell pwd)
UID = $(shell id -u)
GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.npm_version)
all: lint-fix lint test gen
@ -41,10 +42,13 @@ gen-web:
openapitools/openapi-generator-cli generate \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
# npm i runs tsc as part of the installation process
cd web/api && npm i
-o /local/web-api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=@goauthentik/api,npmVersion=${NPM_VERSION}
mkdir -p web/node_modules/@goauthentik/api
python -m scripts.web_api_esm
\cp -fv scripts/web_api_readme.md web-api/README.md
cd web-api && npm i
\cp -rfv web-api/* web/node_modules/@goauthentik/api
gen-outpost:
docker run \

155
Pipfile.lock generated
View File

@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:057196ac15de4de2221a24a3a0a41692414fa1dd697994d062ebd447163265e7",
"sha256:852e776cea4287f74edcb45564f8345fb6b0168dde0fd5bf46668b94c3f21177"
"sha256:4dc7e346e92c01e8a997daa58a4c990151841d2d2962067325d963f665c7287a",
"sha256:79b7e6e0167def749352968ed6eb96954d9e2dd1dca8f297f122414753ce73a3"
],
"index": "pypi",
"version": "==1.18.25"
"version": "==1.18.29"
},
"botocore": {
"hashes": [
"sha256:201e10d3b1b40d65b7c9214be7087d78ed65de00e7362bd1e020741301d09fbc",
"sha256:b9820ee29d70059c9b0e2a69ec13ebf80f4a0bc85f47578f17e951438c506b2d"
"sha256:1f16998b4f5a88e6844196feee7fa5eef6b36034d377f9845c7df12b8803b3be",
"sha256:fec924f63b40bd29b522fa109ecbc45f16eedcbeb22b68c6c79773c22a552b16"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.25"
"version": "==1.21.29"
},
"cachetools": {
"hashes": [
@ -305,22 +305,25 @@
},
"cryptography": {
"hashes": [
"sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
"sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
"sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
"sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
"sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
"sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
"sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
"sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
"sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
"sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586",
"sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3",
"sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
"sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
"sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
"sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e",
"sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b",
"sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
"sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
"sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
"sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
"sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
"sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
"sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
"sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
"sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb",
"sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14",
"sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af",
"sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e",
"sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5",
"sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06",
"sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"
],
"version": "==3.4.7"
"version": "==3.4.8"
},
"dacite": {
"hashes": [
@ -448,11 +451,11 @@
},
"drf-spectacular": {
"hashes": [
"sha256:f080128c42183fcaed6b9e8e5afcd2e5cd68426b1f80bfc85938f25e62db7fe5",
"sha256:fb19aa69fcfcd37b0c9dfb9989c0671e1bb47af332ca2171378c7f840263788c"
"sha256:5b1c27de127c86564be5a967a6fa195cfe161b552d98364282ae9e6ed3d75a85",
"sha256:8588706c27f44adfbb3405bae9ef9cd6506f4b59d4cbd66c59780dce035602d9"
],
"index": "pypi",
"version": "==0.17.3"
"version": "==0.18.0"
},
"duo-client": {
"hashes": [
@ -810,11 +813,11 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
"sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c",
"sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.19"
"markers": "python_full_version >= '3.6.2'",
"version": "==3.0.20"
},
"psycopg2-binary": {
"hashes": [
@ -1417,11 +1420,11 @@
},
"astroid": {
"hashes": [
"sha256:3975a0bd5373bdce166e60c851cfcbaf21ee96de80ec518c1f4cb3e94c3fb334",
"sha256:ab7f36e8a78b8e54a62028ba6beef7561db4cdb6f2a5009ecc44a6f42b5697ef"
"sha256:b6c2d75cd7c2982d09e7d41d70213e863b3ba34d3bd4014e08f167cee966e99e",
"sha256:ecc50f9b3803ebf8ea19aa2c6df5622d8a5c31456a53c741d3be044d96ff0948"
],
"markers": "python_version ~= '3.6'",
"version": "==2.6.6"
"version": "==2.7.2"
},
"attrs": {
"hashes": [
@ -1647,6 +1650,14 @@
"markers": "python_version >= '2.6'",
"version": "==5.6.0"
},
"platformdirs": {
"hashes": [
"sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c",
"sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"
],
"markers": "python_version >= '3.6'",
"version": "==2.2.0"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
@ -1665,11 +1676,11 @@
},
"pylint": {
"hashes": [
"sha256:2e1a0eb2e8ab41d6b5dbada87f066492bb1557b12b76c47c2ee8aa8a11186594",
"sha256:8b838c8983ee1904b2de66cce9d0b96649a91901350e956d78f289c3bc87b48e"
"sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1",
"sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"
],
"index": "pypi",
"version": "==2.9.6"
"version": "==2.10.2"
},
"pylint-django": {
"hashes": [
@ -1747,41 +1758,49 @@
},
"regex": {
"hashes": [
"sha256:026beb631097a4a3def7299aa5825e05e057de3c6d72b139c37813bfa351274b",
"sha256:14caacd1853e40103f59571f169704367e79fb78fac3d6d09ac84d9197cadd16",
"sha256:16d9eaa8c7e91537516c20da37db975f09ac2e7772a0694b245076c6d68f85da",
"sha256:18fdc51458abc0a974822333bd3a932d4e06ba2a3243e9a1da305668bd62ec6d",
"sha256:28e8af338240b6f39713a34e337c3813047896ace09d51593d6907c66c0708ba",
"sha256:3835de96524a7b6869a6c710b26c90e94558c31006e96ca3cf6af6751b27dca1",
"sha256:3905c86cc4ab6d71635d6419a6f8d972cab7c634539bba6053c47354fd04452c",
"sha256:3c09d88a07483231119f5017904db8f60ad67906efac3f1baa31b9b7f7cca281",
"sha256:4551728b767f35f86b8e5ec19a363df87450c7376d7419c3cac5b9ceb4bce576",
"sha256:459bbe342c5b2dec5c5223e7c363f291558bc27982ef39ffd6569e8c082bdc83",
"sha256:4f421e3cdd3a273bace013751c345f4ebeef08f05e8c10757533ada360b51a39",
"sha256:577737ec3d4c195c4aef01b757905779a9e9aee608fa1cf0aec16b5576c893d3",
"sha256:57fece29f7cc55d882fe282d9de52f2f522bb85290555b49394102f3621751ee",
"sha256:7976d410e42be9ae7458c1816a416218364e06e162b82e42f7060737e711d9ce",
"sha256:85f568892422a0e96235eb8ea6c5a41c8ccbf55576a2260c0160800dbd7c4f20",
"sha256:8764a78c5464ac6bde91a8c87dd718c27c1cabb7ed2b4beaf36d3e8e390567f9",
"sha256:8935937dad2c9b369c3d932b0edbc52a62647c2afb2fafc0c280f14a8bf56a6a",
"sha256:8fe58d9f6e3d1abf690174fd75800fda9bdc23d2a287e77758dc0e8567e38ce6",
"sha256:937b20955806381e08e54bd9d71f83276d1f883264808521b70b33d98e4dec5d",
"sha256:9569da9e78f0947b249370cb8fadf1015a193c359e7e442ac9ecc585d937f08d",
"sha256:a3b73390511edd2db2d34ff09aa0b2c08be974c71b4c0505b4a048d5dc128c2b",
"sha256:a4eddbe2a715b2dd3849afbdeacf1cc283160b24e09baf64fa5675f51940419d",
"sha256:a5c6dbe09aff091adfa8c7cfc1a0e83fdb8021ddb2c183512775a14f1435fe16",
"sha256:b63e3571b24a7959017573b6455e05b675050bbbea69408f35f3cb984ec54363",
"sha256:bb350eb1060591d8e89d6bac4713d41006cd4d479f5e11db334a48ff8999512f",
"sha256:bf6d987edd4a44dd2fa2723fca2790f9442ae4de2c8438e53fcb1befdf5d823a",
"sha256:bfa6a679410b394600eafd16336b2ce8de43e9b13f7fb9247d84ef5ad2b45e91",
"sha256:c856ec9b42e5af4fe2d8e75970fcc3a2c15925cbcc6e7a9bcb44583b10b95e80",
"sha256:cea56288eeda8b7511d507bbe7790d89ae7049daa5f51ae31a35ae3c05408531",
"sha256:ea212df6e5d3f60341aef46401d32fcfded85593af1d82b8b4a7a68cd67fdd6b",
"sha256:f35567470ee6dbfb946f069ed5f5615b40edcbb5f1e6e1d3d2b114468d505fc6",
"sha256:fbc20975eee093efa2071de80df7f972b7b35e560b213aafabcec7c0bd00bd8c",
"sha256:ff4a8ad9638b7ca52313d8732f37ecd5fd3c8e3aff10a8ccb93176fd5b3812f6"
"sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd",
"sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642",
"sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1",
"sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321",
"sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529",
"sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36",
"sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a",
"sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30",
"sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce",
"sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376",
"sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd",
"sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586",
"sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7",
"sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9",
"sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea",
"sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94",
"sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3",
"sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f",
"sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267",
"sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc",
"sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23",
"sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882",
"sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc",
"sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe",
"sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759",
"sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456",
"sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239",
"sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb",
"sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948",
"sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0",
"sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183",
"sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92",
"sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade",
"sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044",
"sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee",
"sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033",
"sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2",
"sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5",
"sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2",
"sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504",
"sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"
],
"version": "==2021.8.3"
"version": "==2021.8.21"
},
"requests": {
"hashes": [

View File

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

View File

@ -34,6 +34,7 @@ class RuntimeDict(TypedDict):
class SystemSerializer(PassiveSerializer):
"""Get system information."""
env = SerializerMethodField()
http_headers = SerializerMethodField()
http_host = SerializerMethodField()
http_is_secure = SerializerMethodField()
@ -42,6 +43,10 @@ class SystemSerializer(PassiveSerializer):
server_time = SerializerMethodField()
embedded_outpost_host = SerializerMethodField()
def get_env(self, request: Request) -> dict[str, str]:
"""Get Environment"""
return os.environ.copy()
def get_http_headers(self, request: Request) -> dict[str, str]:
"""Get HTTP Request headers"""
headers = {}

View File

@ -33,7 +33,7 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
raise AuthenticationFailed("Malformed header")
# Accept credentials with username and without
if ":" in auth_credentials:
_, password = auth_credentials.split(":")
_, _, password = auth_credentials.partition(":")
else:
password = auth_credentials
if password == "": # nosec

View File

@ -4,14 +4,9 @@ from django.db.models import QuerySet
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
extend_schema,
inline_serializer,
)
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, FileField, ReadOnlyField
from rest_framework.fields import ReadOnlyField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@ -24,6 +19,7 @@ from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.api.decorators import permission_required
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import FilePathSerializer, FileUploadSerializer
from authentik.core.models import Application, User
from authentik.events.models import EventAction
from authentik.policies.api.exec import PolicyTestResultSerializer
@ -122,7 +118,10 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
# If the current user is superuser, they can set `for_user`
for_user = request.user
if request.user.is_superuser and "for_user" in request.query_params:
for_user = get_object_or_404(User, pk=request.query_params.get("for_user"))
try:
for_user = get_object_or_404(User, pk=request.query_params.get("for_user"))
except ValueError:
return HttpResponseBadRequest("for_user must be numerical")
engine = PolicyEngine(application, for_user, request)
engine.use_cache = False
engine.build()
@ -177,13 +176,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
@permission_required("authentik_core.change_application")
@extend_schema(
request={
"multipart/form-data": inline_serializer(
"SetIcon",
fields={
"file": FileField(required=False),
"clear": BooleanField(default=False),
},
)
"multipart/form-data": FileUploadSerializer,
},
responses={
200: OpenApiResponse(description="Success"),
@ -215,7 +208,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
@permission_required("authentik_core.change_application")
@extend_schema(
request=inline_serializer("SetIconURL", fields={"url": CharField()}),
request=FilePathSerializer,
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),

View File

@ -1,7 +1,10 @@
"""Tokens API Viewset"""
from typing import Any
from django.http.response import Http404
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from rest_framework.request import Request
from rest_framework.response import Response
@ -20,7 +23,16 @@ from authentik.managed.api import ManagedSerializer
class TokenSerializer(ManagedSerializer, ModelSerializer):
"""Token Serializer"""
user = UserSerializer(required=False)
user_obj = UserSerializer(required=False)
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure only API or App password tokens are created."""
request: Request = self.context["request"]
attrs.setdefault("user", request.user)
attrs.setdefault("intent", TokenIntents.INTENT_API)
if attrs.get("intent") not in [TokenIntents.INTENT_API, TokenIntents.INTENT_APP_PASSWORD]:
raise ValidationError(f"Invalid intent {attrs.get('intent')}")
return attrs
class Meta:
@ -31,11 +43,14 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
"identifier",
"intent",
"user",
"user_obj",
"description",
"expires",
"expiring",
]
depth = 2
extra_kwargs = {
"user": {"required": False},
}
class TokenViewSerializer(PassiveSerializer):
@ -69,7 +84,6 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
def perform_create(self, serializer: TokenSerializer):
serializer.save(
user=self.request.user,
intent=TokenIntents.INTENT_API,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)

View File

@ -3,13 +3,20 @@ from json import loads
from typing import Optional
from django.db.models.query import QuerySet
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django.utils.translation import gettext as _
from django_filters.filters import BooleanFilter, CharFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_field
from drf_spectacular.utils import (
OpenApiParameter,
extend_schema,
extend_schema_field,
inline_serializer,
)
from guardian.shortcuts import get_anonymous_user, get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
@ -34,7 +41,14 @@ from authentik.core.api.groups import GroupSerializer
from authentik.core.api.used_by import UsedByMixin
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 Group, Token, TokenIntents, User
from authentik.core.models import (
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
Group,
Token,
TokenIntents,
User,
)
from authentik.events.models import EventAction
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
@ -220,6 +234,51 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
return link, token
@permission_required(None, ["authentik_core.add_user", "authentik_core.add_token"])
@extend_schema(
request=inline_serializer(
"UserServiceAccountSerializer",
{
"name": CharField(required=True),
"create_group": BooleanField(default=False),
},
),
responses={
200: inline_serializer(
"UserServiceAccountResponse",
{
"username": CharField(required=True),
"token": CharField(required=True),
},
)
},
)
@action(detail=False, methods=["POST"], pagination_class=None, filter_backends=[])
def service_account(self, request: Request) -> Response:
"""Create a new user account that is marked as a service account"""
username = request.data.get("name")
create_group = request.data.get("create_group", False)
with atomic():
try:
user = User.objects.create(
username=username,
name=username,
attributes={USER_ATTRIBUTE_SA: True, USER_ATTRIBUTE_TOKEN_EXPIRING: False},
)
if create_group:
group = Group.objects.create(
name=username,
)
group.users.add(user)
token = Token.objects.create(
identifier=f"service-account-{username}-password",
intent=TokenIntents.INTENT_APP_PASSWORD,
user=user,
)
return Response({"username": user.username, "token": token.key})
except (IntegrityError) as exc:
return Response(data={"non_field_errors": [str(exc)]}, status=400)
@extend_schema(responses={200: SessionUserSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name
@ -251,7 +310,9 @@ class UserViewSet(UsedByMixin, ModelViewSet):
# since it caches the full object
if SESSION_IMPERSONATE_USER in request.session:
request.session[SESSION_IMPERSONATE_USER] = new_user
return self.me(request)
serializer = SessionUserSerializer(data={"user": UserSelfSerializer(request.user).data})
serializer.is_valid()
return Response(serializer.data)
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@extend_schema(responses={200: UserMetricsSerializer(many=False)})

View File

@ -2,7 +2,7 @@
from typing import Any
from django.db.models import Model
from rest_framework.fields import CharField, IntegerField
from rest_framework.fields import BooleanField, CharField, FileField, IntegerField
from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError
@ -22,8 +22,18 @@ class PassiveSerializer(Serializer):
def update(self, instance: Model, validated_data: dict) -> Model: # pragma: no cover
return Model()
class Meta:
model = Model
class FileUploadSerializer(PassiveSerializer):
"""Serializer to upload file"""
file = FileField(required=False)
clear = BooleanField(default=False)
class FilePathSerializer(PassiveSerializer):
"""Serializer to upload file"""
url = CharField()
class MetaNameSerializer(PassiveSerializer):

59
authentik/core/auth.py Normal file
View File

@ -0,0 +1,59 @@
"""Authenticate with tokens"""
from typing import Any, Optional
from django.contrib.auth.backends import ModelBackend
from django.http.request import HttpRequest
from authentik.core.models import Token, TokenIntents, User
from authentik.events.utils import cleanse_dict, sanitize_dict
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
class InbuiltBackend(ModelBackend):
"""Inbuilt backend"""
def authenticate(
self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any
) -> Optional[User]:
user = super().authenticate(request, username=username, password=password, **kwargs)
if not user:
return None
self.set_method("password", request)
return user
def set_method(self, method: str, request: Optional[HttpRequest], **kwargs):
"""Set method data on current flow, if possbiel"""
if not request:
return
# Since we can't directly pass other variables to signals, and we want to log the method
# and the token used, we assume we're running in a flow and set a variable in the context
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
flow_plan.context[PLAN_CONTEXT_METHOD] = method
flow_plan.context[PLAN_CONTEXT_METHOD_ARGS] = cleanse_dict(sanitize_dict(kwargs))
request.session[SESSION_KEY_PLAN] = flow_plan
class TokenBackend(InbuiltBackend):
"""Authenticate with token"""
def authenticate(
self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any
) -> Optional[User]:
try:
user = User._default_manager.get_by_natural_key(username)
except User.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
User().set_password(password)
return None
tokens = Token.filter_not_expired(
user=user, key=password, intent=TokenIntents.INTENT_APP_PASSWORD
)
if not tokens.exists():
return None
token = tokens.first()
self.set_method("password", request, token=token)
return token.user

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-08-23 14:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0027_bootstrap_token"),
]
operations = [
migrations.AlterField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
("recovery", "Intent Recovery"),
("app_password", "Intent App Password"),
],
default="verification",
),
),
]

View File

@ -28,6 +28,7 @@ from authentik.core.signals import password_changed
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.flows.models import Flow
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.lib.utils.http import get_client_ip
from authentik.managed.models import ManagedModel
@ -54,7 +55,9 @@ def default_token_duration():
def default_token_key():
"""Default token key"""
return uuid4().hex
# We use generate_id since the chars in the key should be easy
# to use in Emails (for verification) and URLs (for recovery)
return generate_id(128)
class Group(models.Model):
@ -408,6 +411,9 @@ class TokenIntents(models.TextChoices):
# Recovery use for the recovery app
INTENT_RECOVERY = "recovery"
# App-specific passwords
INTENT_APP_PASSWORD = "app_password" # nosec
class Token(ManagedModel, ExpiringModel):
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""

View File

@ -25,7 +25,7 @@ from authentik.flows.planner import (
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
@ -189,7 +189,7 @@ class SourceFlowManager:
kwargs.update(
{
# Since we authenticate the user by their token, they have no backend set
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO,
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_SOURCE: self.source,
PLAN_CONTEXT_REDIRECT: final_redirect,

View File

@ -1,16 +1,13 @@
"""Test Source flow_manager"""
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest
from django.test import TestCase
from django.test.client import RequestFactory
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.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import generate_client_id
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import get_request
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.callback import OAuthSourceFlowManager
@ -22,24 +19,12 @@ class TestSourceFlowManager(TestCase):
super().setUp()
self.source = OAuthSource.objects.create(name="test")
self.factory = RequestFactory()
self.identifier = generate_client_id()
def get_request(self, user: User) -> HttpRequest:
"""Helper to create a get request with session and message middleware"""
request = self.factory.get("/")
request.user = user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request
self.identifier = generate_id()
def test_unauthenticated_enroll(self):
"""Test un-authenticated user enrolling"""
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
@ -52,7 +37,7 @@ class TestSourceFlowManager(TestCase):
)
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.AUTH)
@ -65,7 +50,7 @@ class TestSourceFlowManager(TestCase):
)
user = User.objects.create(username="foo", email="foo@bar.baz")
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(user), self.identifier, {}
self.source, get_request("/", user=user), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
@ -78,7 +63,7 @@ class TestSourceFlowManager(TestCase):
# Without email, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
@ -86,7 +71,7 @@ class TestSourceFlowManager(TestCase):
# With email
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
get_request("/", user=AnonymousUser()),
self.identifier,
{"email": "foo@bar.baz"},
)
@ -101,7 +86,7 @@ class TestSourceFlowManager(TestCase):
# Without username, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
self.source, get_request("/", user=AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
@ -109,7 +94,7 @@ class TestSourceFlowManager(TestCase):
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
get_request("/", user=AnonymousUser()),
self.identifier,
{"username": "foo"},
)
@ -125,7 +110,7 @@ class TestSourceFlowManager(TestCase):
# With non-existent username, enroll
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
get_request("/", user=AnonymousUser()),
self.identifier,
{
"username": "bar",
@ -137,7 +122,7 @@ class TestSourceFlowManager(TestCase):
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
get_request("/", user=AnonymousUser()),
self.identifier,
{"username": "foo"},
)
@ -151,7 +136,7 @@ class TestSourceFlowManager(TestCase):
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
get_request("/", user=AnonymousUser()),
self.identifier,
{"username": "foo"},
)

View File

@ -27,6 +27,14 @@ class TestTokenAPI(APITestCase):
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, True)
def test_token_create_invalid(self):
"""Test token creation endpoint (invalid data)"""
response = self.client.post(
reverse("authentik_api:token-list"),
{"identifier": "test-token", "intent": TokenIntents.INTENT_RECOVERY},
)
self.assertEqual(response.status_code, 400)
def test_token_create_non_expiring(self):
"""Test token creation endpoint"""
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = False

View File

@ -0,0 +1,40 @@
"""Test token auth"""
from django.test import TestCase
from authentik.core.auth import TokenBackend
from authentik.core.models import Token, TokenIntents, User
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.lib.tests.utils import get_request
class TestTokenAuth(TestCase):
"""Test token auth"""
def setUp(self) -> None:
self.user = User.objects.create(username="test-user")
self.token = Token.objects.create(
expiring=False, user=self.user, intent=TokenIntents.INTENT_APP_PASSWORD
)
# To test with session we need to create a request and pass it through all middlewares
self.request = get_request("/")
self.request.session[SESSION_KEY_PLAN] = FlowPlan("test")
def test_token_auth(self):
"""Test auth with token"""
self.assertEqual(
TokenBackend().authenticate(self.request, "test-user", self.token.key), self.user
)
def test_token_auth_none(self):
"""Test auth with token (non-existent user)"""
self.assertIsNone(
TokenBackend().authenticate(self.request, "test-user-foo", self.token.key), self.user
)
def test_token_auth_invalid(self):
"""Test auth with token (invalid token)"""
self.assertIsNone(
TokenBackend().authenticate(self.request, "test-user", self.token.key + "foo"),
self.user,
)

View File

@ -105,3 +105,39 @@ class TestUsersAPI(APITestCase):
+ f"?email_stage={stage.pk}"
)
self.assertEqual(response.status_code, 204)
def test_service_account(self):
"""Service account creation"""
self.client.force_login(self.admin)
response = self.client.post(reverse("authentik_api:user-service-account"))
self.assertEqual(response.status_code, 400)
response = self.client.post(
reverse("authentik_api:user-service-account"),
data={
"name": "test-sa",
"create_group": True,
},
)
self.assertEqual(response.status_code, 200)
self.assertTrue(User.objects.filter(username="test-sa").exists())
def test_service_account_invalid(self):
"""Service account creation (twice with same name, expect error)"""
self.client.force_login(self.admin)
response = self.client.post(
reverse("authentik_api:user-service-account"),
data={
"name": "test-sa",
"create_group": True,
},
)
self.assertEqual(response.status_code, 200)
self.assertTrue(User.objects.filter(username="test-sa").exists())
response = self.client.post(
reverse("authentik_api:user-service-account"),
data={
"name": "test-sa",
"create_group": True,
},
)
self.assertEqual(response.status_code, 400)

View File

@ -10,7 +10,7 @@ from authentik.crypto.api import CertificateKeyPairSerializer
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.lib.generators import generate_key
from authentik.providers.oauth2.models import OAuth2Provider
@ -103,7 +103,7 @@ class TestCrypto(TestCase):
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
client_secret=generate_client_secret(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://localhost",
rsa_key=CertificateKeyPair.objects.first(),

View File

@ -15,6 +15,7 @@ from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
from authentik.stages.user_write.signals import user_write
@ -46,7 +47,13 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
flow_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
if PLAN_CONTEXT_SOURCE in flow_plan.context:
# Login request came from an external source, save it in the context
thread.kwargs["using_source"] = flow_plan.context[PLAN_CONTEXT_SOURCE]
thread.kwargs[PLAN_CONTEXT_SOURCE] = flow_plan.context[PLAN_CONTEXT_SOURCE]
if PLAN_CONTEXT_METHOD in flow_plan.context:
thread.kwargs[PLAN_CONTEXT_METHOD] = flow_plan.context[PLAN_CONTEXT_METHOD]
# Save the login method used
thread.kwargs[PLAN_CONTEXT_METHOD_ARGS] = flow_plan.context.get(
PLAN_CONTEXT_METHOD_ARGS, {}
)
thread.user = user
thread.run()

View File

@ -7,10 +7,10 @@ from django.http.response import HttpResponseBadRequest, JsonResponse
from django.urls import reverse
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, FileField, ReadOnlyField
from rest_framework.fields import ReadOnlyField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@ -20,7 +20,12 @@ from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, LinkSerializer
from authentik.core.api.utils import (
CacheSerializer,
FilePathSerializer,
FileUploadSerializer,
LinkSerializer,
)
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
@ -147,7 +152,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
],
)
@extend_schema(
request={"multipart/form-data": inline_serializer("SetIcon", fields={"file": FileField()})},
request={"multipart/form-data": FileUploadSerializer},
responses={
204: OpenApiResponse(description="Successfully imported flow"),
400: OpenApiResponse(description="Bad request"),
@ -259,13 +264,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
@permission_required("authentik_flows.change_flow")
@extend_schema(
request={
"multipart/form-data": inline_serializer(
"SetIcon",
fields={
"file": FileField(required=False),
"clear": BooleanField(default=False),
},
)
"multipart/form-data": FileUploadSerializer,
},
responses={
200: OpenApiResponse(description="Success"),
@ -301,7 +300,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
@permission_required("authentik_core.change_application")
@extend_schema(
request=inline_serializer("SetIconURL", fields={"url": CharField()}),
request=FilePathSerializer,
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),

View File

@ -11,7 +11,7 @@ class Command(BaseCommand): # pragma: no cover
def handle(self, *args, **options):
"""Apply all flows in order, abort when one fails to import"""
for flow_path in options.get("flows", []):
with open(flow_path, "r") as flow_file:
with open(flow_path, "r", encoding="utf8") as flow_file:
importer = FlowImporter(flow_file.read())
valid = importer.validate()
if not valid:

View File

@ -6,7 +6,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.flows.models import FlowDesignation
from authentik.stages.identification.models import UserFields
from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
@ -26,7 +26,7 @@ def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSc
password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create(
name="default-authentication-password",
defaults={"backends": [BACKEND_DJANGO, BACKEND_LDAP]},
defaults={"backends": [BACKEND_INBUILT, BACKEND_LDAP, BACKEND_APP_PASSWORD]},
)
login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create(

View File

@ -3,7 +3,6 @@ from unittest.mock import MagicMock, Mock, PropertyMock, patch
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import cache
from django.http import HttpRequest
from django.test import RequestFactory, TestCase
from django.urls import reverse
from guardian.shortcuts import get_anonymous_user
@ -13,6 +12,7 @@ from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableExce
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.lib.tests.utils import dummy_get_response
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding
from authentik.policies.types import PolicyResult
@ -24,11 +24,6 @@ CACHE_MOCK = Mock(wraps=cache)
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
def dummy_get_response(request: HttpRequest): # pragma: no cover
"""Dummy get_response for SessionMiddleware"""
return None
class TestFlowPlanner(TestCase):
"""Test planner logic"""

View File

@ -7,9 +7,9 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.transfer.common import DataclassEncoder
from authentik.flows.transfer.exporter import FlowExporter
from authentik.flows.transfer.importer import FlowImporter, transaction_rollback
from authentik.lib.generators import generate_id
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.generators import generate_client_id
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
from authentik.stages.user_login.models import UserLoginStage
@ -31,15 +31,15 @@ class TestFlowTransfer(TransactionTestCase):
def test_export_validate_import(self):
"""Test export and validate it"""
flow_slug = generate_client_id()
flow_slug = generate_id()
with transaction_rollback():
login_stage = UserLoginStage.objects.create(name=generate_client_id())
login_stage = UserLoginStage.objects.create(name=generate_id())
flow = Flow.objects.create(
slug=flow_slug,
designation=FlowDesignation.AUTHENTICATION,
name=generate_client_id(),
title=generate_client_id(),
name=generate_id(),
title=generate_id(),
)
FlowStageBinding.objects.update_or_create(
target=flow,
@ -60,18 +60,18 @@ class TestFlowTransfer(TransactionTestCase):
def test_export_validate_import_policies(self):
"""Test export and validate it"""
flow_slug = generate_client_id()
stage_name = generate_client_id()
flow_slug = generate_id()
stage_name = generate_id()
with transaction_rollback():
flow_policy = ExpressionPolicy.objects.create(
name=generate_client_id(),
name=generate_id(),
expression="return True",
)
flow = Flow.objects.create(
slug=flow_slug,
designation=FlowDesignation.AUTHENTICATION,
name=generate_client_id(),
title=generate_client_id(),
name=generate_id(),
title=generate_id(),
)
PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
@ -111,15 +111,15 @@ class TestFlowTransfer(TransactionTestCase):
)
# Stages
first_stage = PromptStage.objects.create(name=generate_client_id())
first_stage = PromptStage.objects.create(name=generate_id())
first_stage.fields.set([username_prompt, password, password_repeat])
first_stage.save()
flow = Flow.objects.create(
name=generate_client_id(),
slug=generate_client_id(),
name=generate_id(),
slug=generate_id(),
designation=FlowDesignation.ENROLLMENT,
title=generate_client_id(),
title=generate_id(),
)
FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)

View File

@ -16,7 +16,7 @@ def pbflow_tester(file_name: str) -> Callable:
"""This is used instead of subTest for better visibility"""
def tester(self: TestTransferDocs):
with open(file_name, "r") as flow_json:
with open(file_name, "r", encoding="utf8") as flow_json:
importer = FlowImporter(flow_json.read())
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())

View File

@ -25,6 +25,8 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"component",
"flow_set",
"promptstage_set",
"policybindingmodel_ptr_id",
"export_url",
)
for to_remove_name in to_remove:
if to_remove_name in data:

View File

@ -79,7 +79,7 @@ class ConfigLoader:
value = os.getenv(url.netloc, url.query)
if url.scheme == "file":
try:
with open(url.path, "r") as _file:
with open(url.path, "r", encoding="utf8") as _file:
value = _file.read()
except OSError:
self._log("error", f"Failed to read config value from {url.path}")
@ -89,7 +89,7 @@ class ConfigLoader:
def update_from_file(self, path: str):
"""Update config from file contents"""
try:
with open(path) as file:
with open(path, encoding="utf8") as file:
try:
self.update(self.__config, yaml.safe_load(file))
self._log("debug", "Loaded config", file=path)

View File

@ -0,0 +1,18 @@
"""ID/Secret Generators"""
import string
from random import SystemRandom
def generate_id(length=40):
"""Generate a random client ID"""
rand = SystemRandom()
return "".join(rand.choice(string.ascii_letters + string.digits) for x in range(length))
def generate_key(length=128):
"""Generate a suitable client secret"""
rand = SystemRandom()
return "".join(
rand.choice(string.ascii_letters + string.digits + string.punctuation)
for x in range(length)
)

View File

@ -0,0 +1,27 @@
"""Test utils"""
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
from django.test.client import RequestFactory
from guardian.utils import get_anonymous_user
def dummy_get_response(request: HttpRequest): # pragma: no cover
"""Dummy get_response for SessionMiddleware"""
return None
def get_request(*args, user=None, **kwargs):
"""Get a request with usable session"""
request = RequestFactory().get(*args, **kwargs)
if user:
request.user = user
else:
request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request

View File

@ -102,9 +102,11 @@ class DockerController(BaseController):
)
# pylint: disable=too-many-return-statements
def up(self):
def up(self, depth=1):
if self.outpost.managed == MANAGED_OUTPOST:
return None
if depth >= 10:
raise ControllerException("Giving up since we exceeded recursion limit.")
try:
container, has_been_created = self._get_container()
if has_been_created:
@ -120,17 +122,17 @@ class DockerController(BaseController):
should=self.get_container_image(),
)
self.down()
return self.up()
return self.up(depth + 1)
# Check container's ports
if self._comp_ports(container):
self.logger.info("Container has mis-matched ports, re-creating...")
self.down()
return self.up()
return self.up(depth + 1)
# Check that container values match our values
if self._comp_env(container):
self.logger.info("Container has outdated config, re-creating...")
self.down()
return self.up()
return self.up(depth + 1)
if (
container.attrs.get("HostConfig", {})
.get("RestartPolicy", {})
@ -140,7 +142,7 @@ class DockerController(BaseController):
):
self.logger.info("Container has mis-matched restart policy, re-creating...")
self.down()
return self.up()
return self.up(depth + 1)
# Check that container is healthy
if (
container.status == "running"

View File

@ -1,11 +1,13 @@
"""k8s utils"""
from pathlib import Path
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
def get_namespace() -> str:
"""Get the namespace if we're running in a pod, otherwise default to default"""
path = Path("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
path = Path(SERVICE_TOKEN_FILENAME.replace("token", "namespace"))
if path.exists():
with open(path, "r") as _namespace_file:
with open(path, "r", encoding="utf8") as _namespace_file:
return _namespace_file.read()
return "default"

View File

@ -25,7 +25,7 @@ class DockerInlineTLS:
def write_file(self, name: str, contents: str) -> str:
"""Wrapper for mkstemp that uses fdopen"""
path = Path(gettempdir(), name)
with open(path, "w") as _file:
with open(path, "w", encoding="utf8") as _file:
_file.write(contents)
return str(path)

View File

@ -0,0 +1,15 @@
"""Repair missing permissions"""
from django.apps import apps
from django.contrib.auth.management import create_permissions
from django.core.management.base import BaseCommand, no_translations
class Command(BaseCommand): # pragma: no cover
"""Repair missing permissions"""
@no_translations
def handle(self, *args, **options):
"""Check permissions for all apps"""
for app in apps.get_app_configs():
self.stdout.write(f"Checking app {app.name} ({app.label})\n")
create_permissions(app, verbosity=0)

View File

@ -37,9 +37,11 @@ from authentik.core.models import (
User,
)
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey
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
@ -358,7 +360,24 @@ class Outpost(ManagedModel):
code_name = (
f"{model_or_perm._meta.app_label}." f"view_{model_or_perm._meta.model_name}"
)
assign_perm(code_name, user, model_or_perm)
try:
assign_perm(code_name, user, model_or_perm)
except Permission.DoesNotExist as exc:
LOGGER.warning(
"permission doesn't exist",
code_name=code_name,
user=user,
model=model_or_perm,
)
Event.new(
action=EventAction.SYSTEM_EXCEPTION,
message=(
"While setting the permissions for the service-account, a "
"permission was not found: Check "
"https://goauthentik.io/docs/troubleshooting/missing_permission"
)
+ exception_to_string(exc),
).set_user(user).save()
else:
app_label, perm = model_or_perm.split(".")
permission = Permission.objects.filter(

View File

@ -227,7 +227,7 @@ def outpost_local_connection():
kubeconfig_local_name = f"k8s-{gethostname()}"
if not KubernetesServiceConnection.objects.filter(name=kubeconfig_local_name).exists():
LOGGER.debug("Creating kubeconfig Service Connection")
with open(kubeconfig_path, "r") as _kubeconfig:
with open(kubeconfig_path, "r", encoding="utf8") as _kubeconfig:
KubernetesServiceConnection.objects.create(
name=kubeconfig_local_name,
kubeconfig=yaml.safe_load(_kubeconfig),

View File

@ -2,9 +2,9 @@
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.lib.generators import generate_key
from authentik.policies.hibp.models import HaveIBeenPwendPolicy
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.providers.oauth2.generators import generate_client_secret
class TestHIBPPolicy(TestCase):
@ -37,7 +37,7 @@ class TestHIBPPolicy(TestCase):
name="test_true",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = generate_client_secret()
request.context["password"] = generate_key()
result: PolicyResult = policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -3,7 +3,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.fields import CharField
from rest_framework.generics import get_object_or_404
from rest_framework.request import Request
from rest_framework.response import Response
@ -49,12 +49,12 @@ class OAuth2ProviderSerializer(ProviderSerializer):
class OAuth2ProviderSetupURLs(PassiveSerializer):
"""OAuth2 Provider Metadata serializer"""
issuer = ReadOnlyField()
authorize = ReadOnlyField()
token = ReadOnlyField()
user_info = ReadOnlyField()
provider_info = ReadOnlyField()
logout = ReadOnlyField()
issuer = CharField(read_only=True)
authorize = CharField(read_only=True)
token = CharField(read_only=True)
user_info = CharField(read_only=True)
provider_info = CharField(read_only=True)
logout = CharField(read_only=True)
class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):

View File

@ -1,15 +0,0 @@
"""OAuth2 Client ID/Secret Generators"""
import string
from random import SystemRandom
def generate_client_id():
"""Generate a random client ID"""
rand = SystemRandom()
return "".join(rand.choice(string.ascii_letters + string.digits) for x in range(40))
def generate_client_secret():
"""Generate a suitable client secret"""
rand = SystemRandom()
return "".join(rand.choice(string.ascii_letters + string.digits) for x in range(128))

View File

@ -7,8 +7,8 @@ from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
import authentik.lib.generators
import authentik.lib.utils.time
import authentik.providers.oauth2.generators
class Migration(migrations.Migration):
@ -55,7 +55,7 @@ class Migration(migrations.Migration):
(
"client_id",
models.CharField(
default=authentik.providers.oauth2.generators.generate_client_id,
default=authentik.lib.generators.generate_id,
max_length=255,
unique=True,
verbose_name="Client ID",
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
"client_secret",
models.CharField(
blank=True,
default=authentik.providers.oauth2.generators.generate_client_secret,
default=authentik.lib.generators.generate_key,
max_length=255,
verbose_name="Client Secret",
),

View File

@ -22,10 +22,10 @@ from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
from authentik.providers.oauth2.apps import AuthentikProviderOAuth2Config
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
from authentik.providers.oauth2.generators import generate_client_id, generate_client_secret
class ClientTypes(models.TextChoices):
@ -138,13 +138,13 @@ class OAuth2Provider(Provider):
max_length=255,
unique=True,
verbose_name=_("Client ID"),
default=generate_client_id,
default=generate_id,
)
client_secret = models.CharField(
max_length=255,
blank=True,
verbose_name=_("Client Secret"),
default=generate_client_secret,
default=generate_key,
)
jwt_alg = models.CharField(
max_length=10,

View File

@ -7,8 +7,8 @@ from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.errors import AuthorizeError, ClientIdError, RedirectUriError
from authentik.providers.oauth2.generators import generate_client_id, generate_client_secret
from authentik.providers.oauth2.models import (
AuthorizationCode,
GrantTypes,
@ -183,7 +183,7 @@ class TestAuthorize(OAuthTestCase):
redirect_uris="foo://localhost",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_client_id()
state = generate_id()
user = User.objects.get(username="akadmin")
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
@ -215,13 +215,13 @@ class TestAuthorize(OAuthTestCase):
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
client_secret=generate_client_secret(),
client_secret=generate_key(),
authorization_flow=flow,
redirect_uris="http://localhost",
rsa_key=CertificateKeyPair.objects.first(),
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_client_id()
state = generate_id()
user = User.objects.get(username="akadmin")
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow

View File

@ -9,12 +9,12 @@ from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.providers.oauth2.constants import (
GRANT_TYPE_AUTHORIZATION_CODE,
GRANT_TYPE_REFRESH_TOKEN,
)
from authentik.providers.oauth2.errors import TokenError
from authentik.providers.oauth2.generators import generate_client_id, generate_client_secret
from authentik.providers.oauth2.models import AuthorizationCode, OAuth2Provider, RefreshToken
from authentik.providers.oauth2.tests.utils import OAuthTestCase
from authentik.providers.oauth2.views.token import TokenParams
@ -32,8 +32,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://testserver",
rsa_key=CertificateKeyPair.objects.first(),
@ -53,14 +53,14 @@ class TestToken(OAuthTestCase):
params = TokenParams.parse(request, provider, provider.client_id, provider.client_secret)
self.assertEqual(params.provider, provider)
with self.assertRaises(TokenError):
TokenParams.parse(request, provider, provider.client_id, generate_client_secret())
TokenParams.parse(request, provider, provider.client_id, generate_key())
def test_request_auth_code_invalid(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://testserver",
rsa_key=CertificateKeyPair.objects.first(),
@ -82,8 +82,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
@ -93,7 +93,7 @@ class TestToken(OAuthTestCase):
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
refresh_token=generate_id(),
)
request = self.factory.post(
"/",
@ -111,8 +111,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
@ -153,8 +153,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
@ -167,7 +167,7 @@ class TestToken(OAuthTestCase):
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
refresh_token=generate_id(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -202,8 +202,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
@ -213,7 +213,7 @@ class TestToken(OAuthTestCase):
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
refresh_token=generate_id(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -247,8 +247,8 @@ class TestToken(OAuthTestCase):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://testserver",
rsa_key=CertificateKeyPair.objects.first(),
@ -261,7 +261,7 @@ class TestToken(OAuthTestCase):
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
refresh_token=generate_id(),
)
# Create initial refresh token
response = self.client.post(

View File

@ -9,8 +9,8 @@ from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import generate_client_id, generate_client_secret
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken, ScopeMapping
from authentik.providers.oauth2.tests.utils import OAuthTestCase
@ -24,8 +24,8 @@ class TestUserinfo(OAuthTestCase):
self.app = Application.objects.create(name="test", slug="test")
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
authorization_flow=Flow.objects.first(),
redirect_uris="",
rsa_key=CertificateKeyPair.objects.first(),
@ -38,8 +38,8 @@ class TestUserinfo(OAuthTestCase):
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_client_id(),
refresh_token=generate_client_id(),
access_token=generate_id(),
refresh_token=generate_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(

View File

@ -103,8 +103,8 @@ def extract_client_auth(request: HttpRequest) -> tuple[str, str]:
if re.compile(r"^Basic\s{1}.+$").match(auth_header):
b64_user_pass = auth_header.split()[1]
try:
user_pass = b64decode(b64_user_pass).decode("utf-8").split(":")
client_id, client_secret = user_pass
user_pass = b64decode(b64_user_pass).decode("utf-8").partition(":")
client_id, _, client_secret = user_pass
except (ValueError, Error):
client_id = client_secret = "" # nosec
else:

View File

@ -11,7 +11,7 @@ from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, FileField, ReadOnlyField, SerializerMethodField
from rest_framework.fields import CharField, FileField, SerializerMethodField
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import AllowAny
from rest_framework.relations import SlugRelatedField
@ -70,8 +70,8 @@ class SAMLProviderSerializer(ProviderSerializer):
class SAMLMetadataSerializer(PassiveSerializer):
"""SAML Provider Metadata serializer"""
metadata = ReadOnlyField()
download_url = ReadOnlyField(required=False)
metadata = CharField(read_only=True)
download_url = CharField(read_only=True, required=False)
class SAMLProviderImportSerializer(PassiveSerializer):

View File

@ -1,16 +1,14 @@
"""Test AuthN Request generator and parser"""
from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import QueryDict
from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from authentik.core.models import User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.lib.tests.utils import get_request
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
@ -99,11 +97,7 @@ class TestAuthNRequest(TestCase):
def test_signed_valid(self):
"""Test generated AuthNRequest with valid signature"""
http_request = self.factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -117,12 +111,7 @@ class TestAuthNRequest(TestCase):
def test_request_full_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -145,12 +134,7 @@ class TestAuthNRequest(TestCase):
def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID"""
http_request = self.factory.get("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -179,11 +163,7 @@ class TestAuthNRequest(TestCase):
def test_signed_valid_detached(self):
"""Test generated AuthNRequest with valid signature (detached)"""
http_request = self.factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -243,12 +223,7 @@ class TestAuthNRequest(TestCase):
def test_request_attributes(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/", user=User.objects.get(username="akadmin"))
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -264,12 +239,7 @@ class TestAuthNRequest(TestCase):
def test_request_attributes_invalid(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/", user=User.objects.get(username="akadmin"))
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")

View File

@ -1,18 +1,16 @@
"""Test Requests and Responses against schema"""
from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from lxml import etree # nosec
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.lib.tests.utils import get_request
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
from authentik.providers.saml.tests.test_auth_n_request import dummy_get_response
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.request import RequestProcessor
@ -43,11 +41,7 @@ class TestSchema(TestCase):
def test_request_schema(self):
"""Test generated AuthNRequest against Schema"""
http_request = self.factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
@ -60,12 +54,7 @@ class TestSchema(TestCase):
def test_response_schema(self):
"""Test generated AuthNRequest against Schema"""
http_request = self.factory.get("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
http_request = get_request("/")
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from django.views import View
from authentik.core.models import Token, TokenIntents
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
class UseTokenView(View):
@ -19,7 +19,7 @@ class UseTokenView(View):
if not tokens.exists():
raise Http404
token = tokens.first()
login(request, token.user, backend=BACKEND_DJANGO)
login(request, token.user, backend=BACKEND_INBUILT)
token.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("authentik_core:if-admin")

View File

View File

@ -0,0 +1,40 @@
"""
ASGI config for authentik project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import django
from asgiref.compatibility import guarantee_single_callable
from channels.routing import ProtocolTypeRouter, URLRouter
from defusedxml import defuse_stdlib
from django.core.asgi import get_asgi_application
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from authentik.root.asgi.error_handler import ASGIErrorHandler
from authentik.root.asgi.logger import ASGILogger
# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py
defuse_stdlib()
django.setup()
# pylint: disable=wrong-import-position
from authentik.root import websocket # noqa # isort:skip
application = ASGIErrorHandler(
ASGILogger(
guarantee_single_callable(
SentryAsgiMiddleware(
ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": URLRouter(websocket.websocket_urlpatterns),
}
)
)
)
)
)

View File

@ -0,0 +1,38 @@
"""ASGI Error handler"""
from structlog.stdlib import get_logger
from authentik.root.asgi.types import ASGIApp, Receive, Scope, Send
LOGGER = get_logger("authentik.asgi")
class ASGIErrorHandler:
"""ASGI Error handler"""
app: ASGIApp
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
try:
return await self.app(scope, receive, send)
except Exception as exc: # pylint: disable=broad-except
LOGGER.warning("Fatal ASGI exception", exc=exc)
return await self.error_handler(scope, send)
async def error_handler(self, scope: Scope, send: Send) -> None:
"""Return a generic error message"""
if scope.get("scheme", "http") == "http":
return await send(
{
"type": "http.request",
"body": b"Internal server error",
"more_body": False,
}
)
return await send(
{
"type": "websocket.close",
}
)

View File

@ -1,41 +1,10 @@
"""
ASGI config for authentik project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import typing
"""ASGI Logger"""
from time import time
import django
from asgiref.compatibility import guarantee_single_callable
from channels.routing import ProtocolTypeRouter, URLRouter
from defusedxml import defuse_stdlib
from django.core.asgi import get_asgi_application
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from structlog.stdlib import get_logger
from authentik.core.middleware import RESPONSE_HEADER_ID
# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py
defuse_stdlib()
django.setup()
# pylint: disable=wrong-import-position
from authentik.root import websocket # noqa # isort:skip
# See https://github.com/encode/starlette/blob/master/starlette/types.py
Scope = typing.MutableMapping[str, typing.Any]
Message = typing.MutableMapping[str, typing.Any]
Receive = typing.Callable[[], typing.Awaitable[Message]]
Send = typing.Callable[[Message], typing.Awaitable[None]]
ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]]
from authentik.root.asgi.types import ASGIApp, Message, Receive, Scope, Send
ASGI_IP_HEADERS = (
b"x-forwarded-for",
@ -86,7 +55,7 @@ class ASGILogger:
# https://code.djangoproject.com/ticket/31508
# https://github.com/encode/uvicorn/issues/266
return
await self.app(scope, receive, send_hooked)
return await self.app(scope, receive, send_hooked)
def _get_ip(self, scope: Scope) -> str:
client_ip = None
@ -115,17 +84,3 @@ class ASGILogger:
runtime=runtime,
**kwargs,
)
application = ASGILogger(
guarantee_single_callable(
SentryAsgiMiddleware(
ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": URLRouter(websocket.websocket_urlpatterns),
}
)
)
)
)

View File

@ -0,0 +1,11 @@
"""ASGI Types"""
import typing
# See https://github.com/encode/starlette/blob/master/starlette/types.py
Scope = typing.MutableMapping[str, typing.Any]
Message = typing.MutableMapping[str, typing.Any]
Receive = typing.Callable[[], typing.Awaitable[Message]]
Send = typing.Callable[[Message], typing.Awaitable[None]]
ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]]

View File

@ -32,6 +32,7 @@ from authentik.core.middleware import structlog_add_request_id
from authentik.lib.config import CONFIG
from authentik.lib.logging import add_process_id
from authentik.lib.sentry import before_send
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
def j_print(event: str, log_level: str = "info", **kwargs):
@ -73,7 +74,9 @@ LANGUAGE_COOKIE_NAME = f"authentik_language{_cookie_suffix}"
SESSION_COOKIE_NAME = f"authentik_session{_cookie_suffix}"
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
BACKEND_INBUILT,
BACKEND_APP_PASSWORD,
BACKEND_LDAP,
"guardian.backends.ObjectPermissionBackend",
]
@ -251,7 +254,7 @@ TEMPLATES = [
},
]
ASGI_APPLICATION = "authentik.root.asgi.application"
ASGI_APPLICATION = "authentik.root.asgi.app.application"
CHANNEL_LAYERS = {
"default": {

View File

@ -27,7 +27,10 @@ class LDAPSourceSerializer(SourceSerializer):
"""Check that only a single source has password_sync on"""
sync_users_password = attrs.get("sync_users_password", True)
if sync_users_password:
if LDAPSource.objects.filter(sync_users_password=True).exists():
sources = LDAPSource.objects.filter(sync_users_password=True)
if self.instance:
sources = sources.exclude(pk=self.instance.pk)
if sources.exists():
raise ValidationError(
"Only a single LDAP Source with password synchronization is allowed"
)

View File

@ -2,10 +2,10 @@
from typing import Optional
import ldap3
from django.contrib.auth.backends import ModelBackend
from django.http import HttpRequest
from structlog.stdlib import get_logger
from authentik.core.auth import InbuiltBackend
from authentik.core.models import User
from authentik.sources.ldap.models import LDAPSource
@ -13,7 +13,7 @@ LOGGER = get_logger()
LDAP_DISTINGUISHED_NAME = "distinguishedName"
class LDAPBackend(ModelBackend):
class LDAPBackend(InbuiltBackend):
"""Authenticate users against LDAP Server"""
def authenticate(self, request: HttpRequest, **kwargs):
@ -24,6 +24,7 @@ class LDAPBackend(ModelBackend):
LOGGER.debug("LDAP Auth attempt", source=source)
user = self.auth_user(source, **kwargs)
if user:
self.set_method("ldap", request, source=source)
return user
return None

View File

@ -1,10 +1,6 @@
"""LDAP Settings"""
from celery.schedules import crontab
AUTHENTICATION_BACKENDS = [
"authentik.sources.ldap.auth.LDAPBackend",
]
CELERY_BEAT_SCHEDULE = {
"sources_ldap_sync": {
"task": "authentik.sources.ldap.tasks.ldap_sync_all",

View File

@ -1,11 +1,11 @@
"""LDAP Source API tests"""
from rest_framework.test import APITestCase
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.lib.generators import generate_key
from authentik.sources.ldap.api import LDAPSourceSerializer
from authentik.sources.ldap.models import LDAPSource
LDAP_PASSWORD = generate_client_secret()
LDAP_PASSWORD = generate_key()
class LDAPAPITests(APITestCase):

View File

@ -5,15 +5,15 @@ from django.db.models import Q
from django.test import TestCase
from authentik.core.models import User
from authentik.lib.generators import generate_key
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.ldap.auth import LDAPBackend
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
from authentik.sources.ldap.tests.mock_slapd import mock_slapd_connection
LDAP_PASSWORD = generate_client_secret()
LDAP_PASSWORD = generate_key()
class LDAPSyncTests(TestCase):

View File

@ -4,12 +4,12 @@ from unittest.mock import PropertyMock, patch
from django.test import TestCase
from authentik.core.models import User
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.lib.generators import generate_key
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.password import LDAPPasswordChanger
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
LDAP_PASSWORD = generate_client_secret()
LDAP_PASSWORD = generate_key()
LDAP_CONNECTION_PATCH = PropertyMock(return_value=mock_ad_connection(LDAP_PASSWORD))

View File

@ -6,8 +6,8 @@ from django.test import TestCase
from authentik.core.models import Group, User
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_key
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
@ -16,7 +16,7 @@ from authentik.sources.ldap.tasks import ldap_sync_all
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
from authentik.sources.ldap.tests.mock_slapd import mock_slapd_connection
LDAP_PASSWORD = generate_client_secret()
LDAP_PASSWORD = generate_key()
class LDAPSyncTests(TestCase):

View File

@ -4,7 +4,7 @@ import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
import authentik.providers.oauth2.generators
import authentik.lib.generators
class Migration(migrations.Migration):
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
(
"client_id",
models.TextField(
default=authentik.providers.oauth2.generators.generate_client_id,
default=authentik.lib.generators.generate_id,
help_text="Client identifier used to talk to Plex.",
),
),

View File

@ -3,7 +3,7 @@
import django.contrib.postgres.fields
from django.db import migrations, models
import authentik.providers.oauth2.generators
import authentik.lib.generators
class Migration(migrations.Migration):

View File

@ -11,7 +11,7 @@ from rest_framework.serializers import BaseSerializer
from authentik.core.models import Source, UserSourceConnection
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.providers.oauth2.generators import generate_client_id
from authentik.lib.generators import generate_id
class PlexAuthenticationChallenge(Challenge):
@ -32,7 +32,7 @@ class PlexSource(Source):
"""Authenticate against plex.tv"""
client_id = models.TextField(
default=generate_client_id,
default=generate_id,
help_text=_("Client identifier used to talk to Plex."),
)
allowed_servers = ArrayField(

View File

@ -4,7 +4,7 @@ from requests.exceptions import RequestException
from requests_mock import Mocker
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.lib.generators import generate_key
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth
from authentik.sources.plex.tasks import check_plex_token_all
@ -41,7 +41,7 @@ class TestPlexSource(TestCase):
def test_get_user_info(self):
"""Test get_user_info"""
token = generate_client_secret()
token = generate_key()
api = PlexAuth(self.source, token)
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/user", json=USER_INFO_RESPONSE)
@ -55,7 +55,7 @@ class TestPlexSource(TestCase):
def test_check_server_overlap(self):
"""Test check_server_overlap"""
token = generate_client_secret()
token = generate_key()
api = PlexAuth(self.source, token)
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/resources", json=RESOURCES_RESPONSE)

View File

@ -53,6 +53,11 @@ class SAMLSourceViewSet(UsedByMixin, ModelViewSet):
return Response(
{
"metadata": metadata,
"download_url": reverse("authentik_sources_saml:metadata"),
"download_url": reverse(
"authentik_sources_saml:metadata",
kwargs={
"source_slug": source.slug,
},
),
}
)

View File

@ -39,7 +39,7 @@ from authentik.sources.saml.processors.constants import (
from authentik.sources.saml.processors.request import SESSION_REQUEST_ID
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_login.stage import BACKEND_DJANGO
from authentik.stages.user_login.stage import BACKEND_INBUILT
LOGGER = get_logger()
if TYPE_CHECKING:
@ -136,7 +136,7 @@ class ResponseProcessor:
self._source.authentication_flow,
**{
PLAN_CONTEXT_PENDING_USER: user,
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO,
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
},
)
@ -199,7 +199,7 @@ class ResponseProcessor:
self._source.authentication_flow,
**{
PLAN_CONTEXT_PENDING_USER: matching_users.first(),
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO,
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_REDIRECT: final_redirect,
},
)

View File

@ -1,7 +1,6 @@
"""Test validator stage"""
from unittest.mock import MagicMock, patch
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls.base import reverse
@ -12,8 +11,8 @@ from rest_framework.exceptions import ValidationError
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import generate_client_id, generate_client_secret
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
from authentik.stages.authenticator_validate.challenge import (
@ -97,11 +96,8 @@ class AuthenticatorValidateStageTests(TestCase):
def test_device_challenge_webauthn(self):
"""Test webauthn"""
request = self.request_factory.get("/")
request = get_request("/")
request.user = self.user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
@ -136,8 +132,8 @@ class AuthenticatorValidateStageTests(TestCase):
request = self.request_factory.get("/")
stage = AuthenticatorDuoStage.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
client_id=generate_id(),
client_secret=generate_key(),
api_hostname="",
)
duo_device = DuoDevice.objects.create(

View File

@ -14,7 +14,7 @@ def inline_static_ascii(path: str) -> str:
If no file could be found, original path is returned"""
result = Path(finders.find(path))
if result:
with open(result) as _file:
with open(result, encoding="utf8") as _file:
return _file.read()
return path
@ -25,7 +25,7 @@ def inline_static_binary(path: str) -> str:
path is returned."""
result = Path(finders.find(path))
if result and result.is_file():
with open(result) as _file:
with open(result, encoding="utf8") as _file:
b64content = b64encode(_file.read().encode())
return f"data:image/{result.suffix};base64,{b64content.decode('utf-8')}"
return path

View File

@ -6,10 +6,10 @@ from django.utils.encoding import force_str
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.lib.generators import generate_key
from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
@ -18,7 +18,7 @@ class TestIdentificationStage(TestCase):
def setUp(self):
super().setUp()
self.password = generate_client_secret()
self.password = generate_key()
self.user = User.objects.create_user(
username="unittest", email="test@beryju.org", password=self.password
)
@ -68,7 +68,7 @@ class TestIdentificationStage(TestCase):
def test_valid_with_password(self):
"""Test with valid email and password in single step"""
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO])
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.stage.password_stage = pw_stage
self.stage.save()
form_data = {"uid_field": self.user.email, "password": self.password}
@ -86,7 +86,7 @@ class TestIdentificationStage(TestCase):
def test_invalid_with_password(self):
"""Test with valid email and invalid password in single step"""
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO])
pw_stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.stage.password_stage = pw_stage
self.stage.save()
form_data = {

View File

@ -17,7 +17,7 @@ from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -45,7 +45,7 @@ class TestUserLoginStage(TestCase):
"""Test without any invitation, continue_flow_without_invitation not set."""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
@ -74,7 +74,7 @@ class TestUserLoginStage(TestCase):
self.stage.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View File

@ -1,3 +1,4 @@
"""Backend paths"""
BACKEND_DJANGO = "django.contrib.auth.backends.ModelBackend"
BACKEND_INBUILT = "authentik.core.auth.InbuiltBackend"
BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend"
BACKEND_APP_PASSWORD = "authentik.core.auth.TokenBackend" # nosec

View File

@ -12,6 +12,8 @@ def rename_default_prompt_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEdi
if not stages.exists():
return
stage = stages.first()
if PromptStage.objects.using(db_alias).filter(name="default-password-change-prompt").exists():
return
stage.name = "default-password-change-prompt"
stage.save()

View File

@ -0,0 +1,48 @@
# Generated by Django 3.2.6 on 2021-08-23 14:34
import django.contrib.postgres.fields
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT
def update_default_backends(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
PasswordStage = apps.get_model("authentik_stages_password", "passwordstage")
db_alias = schema_editor.connection.alias
stages = PasswordStage.objects.using(db_alias).filter(name="default-authentication-password")
if not stages.exists():
return
stage = stages.first()
stage.backends.append(BACKEND_APP_PASSWORD)
stage.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_password", "0006_passwordchange_rename"),
]
operations = [
migrations.AlterField(
model_name="passwordstage",
name="backends",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(
choices=[
("authentik.core.auth.InbuiltBackend", "User database + standard password"),
("authentik.core.auth.TokenBackend", "User database + app passwords"),
(
"authentik.sources.ldap.auth.LDAPBackend",
"User database + LDAP password",
),
]
),
help_text="Selection of backends to test the password against.",
size=None,
),
),
migrations.RunPython(update_default_backends),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 3.2.6 on 2021-08-23 14:34
import django.contrib.postgres.fields
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.stages.password import BACKEND_INBUILT
def replace_inbuilt(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
PasswordStage = apps.get_model("authentik_stages_password", "passwordstage")
db_alias = schema_editor.connection.alias
for stage in PasswordStage.objects.using(db_alias).all():
if "django.contrib.auth.backends.ModelBackend" not in stage.backends:
continue
stage.backends.remove("django.contrib.auth.backends.ModelBackend")
stage.backends.append(BACKEND_INBUILT)
stage.backends.sort()
stage.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_password", "0007_app_password"),
]
operations = [
migrations.RunPython(replace_inbuilt),
]

View File

@ -9,19 +9,23 @@ from rest_framework.serializers import BaseSerializer
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
def get_authentication_backends():
"""Return all available authentication backends as tuple set"""
return [
(
BACKEND_DJANGO,
_("authentik-internal Userdatabase"),
BACKEND_INBUILT,
_("User database + standard password"),
),
(
BACKEND_APP_PASSWORD,
_("User database + app passwords"),
),
(
BACKEND_LDAP,
_("authentik LDAP"),
_("User database + LDAP password"),
),
]

View File

@ -27,6 +27,8 @@ from authentik.stages.password.models import PasswordStage
LOGGER = get_logger()
PLAN_CONTEXT_AUTHENTICATION_BACKEND = "user_backend"
PLAN_CONTEXT_METHOD = "auth_method"
PLAN_CONTEXT_METHOD_ARGS = "auth_method_args"
SESSION_INVALID_TRIES = "user_invalid_tries"

View File

@ -13,8 +13,8 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.stages.password import BACKEND_DJANGO
from authentik.lib.generators import generate_key
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
@ -25,7 +25,7 @@ class TestPasswordStage(TestCase):
def setUp(self):
super().setUp()
self.password = generate_client_secret()
self.password = generate_key()
self.user = User.objects.create_user(
username="unittest", email="test@beryju.org", password=self.password
)
@ -36,7 +36,7 @@ class TestPasswordStage(TestCase):
slug="test-password",
designation=FlowDesignation.AUTHENTICATION,
)
self.stage = PasswordStage.objects.create(name="password", backends=[BACKEND_DJANGO])
self.stage = PasswordStage.objects.create(name="password", backends=[BACKEND_INBUILT])
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
@patch(
@ -158,7 +158,7 @@ class TestPasswordStage(TestCase):
TO_STAGE_RESPONSE_MOCK,
)
@patch(
"django.contrib.auth.backends.ModelBackend.authenticate",
"authentik.core.auth.InbuiltBackend.authenticate",
MOCK_BACKEND_AUTHENTICATE,
)
def test_permission_denied(self):

View File

@ -8,7 +8,7 @@ from structlog.stdlib import get_logger
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
LOGGER = get_logger()
@ -26,7 +26,7 @@ class UserLoginStageView(StageView):
LOGGER.debug(message)
return self.executor.stage_invalid()
backend = self.executor.plan.context.get(
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_DJANGO
PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_INBUILT
)
login(
self.request,
@ -40,6 +40,7 @@ class UserLoginStageView(StageView):
self.request.session.set_expiry(delta)
LOGGER.debug(
"Logged in",
backend=backend,
user=self.executor.plan.context[PLAN_CONTEXT_PENDING_USER],
flow_slug=self.executor.flow.slug,
session_duration=self.executor.current_stage.session_duration,

View File

@ -9,7 +9,7 @@ from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.password import BACKEND_DJANGO
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.user_logout.models import UserLogoutStage
@ -34,7 +34,7 @@ class TestUserLogoutStage(TestCase):
"""Test with a valid pending user and backend"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO
plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View File

@ -1,7 +1,6 @@
"""Write stage logic"""
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.backends import ModelBackend
from django.db import transaction
from django.db.utils import IntegrityError
from django.http import HttpRequest, HttpResponse
@ -13,7 +12,7 @@ from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnec
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.lib.utils.reflection import class_to_path
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.signals import user_write
@ -42,9 +41,7 @@ class UserWriteStageView(StageView):
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
is_active=not self.executor.current_stage.create_users_as_inactive
)
self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = class_to_path(
ModelBackend
)
self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_INBUILT
LOGGER.debug(
"Created new user",
flow_slug=self.executor.flow.slug,

View File

@ -309,9 +309,7 @@ stages:
displayName: Build static files for e2e
inputs:
script: |
make gen-web
cd web
cd api && npm i && cd ..
npm i
npm run build
- task: CmdLine@2
@ -397,17 +395,6 @@ stages:
- task: CmdLine@2
inputs:
script: bash <(curl -s https://codecov.io/bash)
- task: CmdLine@2
continueOnError: true
inputs:
script: |
npm install -g @zeus-ci/cli
npx zeus job update -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-cobertura+xml" coverage.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-e2e/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-integration/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-unittest/unittest.xml
npx zeus job update --status=passed -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
- stage: Build
jobs:
- job: build_server

View File

@ -10,7 +10,7 @@ services:
networks:
- internal
environment:
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
- POSTGRES_PASSWORD=${PG_PASS:?database password required}
- POSTGRES_USER=${PG_USER:-authentik}
- POSTGRES_DB=${PG_DB:-authentik}
env_file:
@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.1-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.1}
restart: unless-stopped
command: server
environment:
@ -44,7 +44,7 @@ services:
- "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.1-rc1}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.1}
restart: unless-stopped
command: worker
networks:

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/runtime v0.19.30
github.com/go-openapi/strfmt v0.20.1
github.com/go-openapi/strfmt v0.20.2
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-redis/redis/v7 v7.4.0 // indirect

4
go.sum
View File

@ -227,8 +227,8 @@ github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
github.com/go-openapi/strfmt v0.20.1 h1:1VgxvehFne1mbChGeCmZ5pc0LxUf6yaACVSIYAR91Xc=
github.com/go-openapi/strfmt v0.20.1/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
github.com/go-openapi/strfmt v0.20.2 h1:6XZL+fF4VZYFxKQGLAUB358hOrRh/wS51uWEtlONADE=
github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=

View File

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

View File

@ -28,7 +28,7 @@ func NewGoUnicorn() *GoUnicorn {
func (g *GoUnicorn) initCmd() {
command := "gunicorn"
args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"}
args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi.app:application"}
if config.G.Debug {
command = "python"
args = []string{"manage.py", "runserver", "localhost:8000"}

View File

@ -23,11 +23,12 @@ type BindRequest struct {
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.bind",
sentry.TransactionName("authentik.providers.ldap.bind"))
rid := uuid.New().String()
span.SetTag("request_uid", rid)
span.SetTag("user.username", bindDN)
defer span.Finish()
bindDN = strings.ToLower(bindDN)
rid := uuid.New().String()
req := BindRequest{
BindDN: bindDN,
BindPW: bindPW,

View File

@ -24,12 +24,13 @@ type SearchRequest struct {
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search"))
rid := uuid.New().String()
span.SetTag("request_uid", rid)
span.SetTag("user.username", bindDN)
span.SetTag("ak_filter", searchReq.Filter)
span.SetTag("ak_base_dn", searchReq.BaseDN)
bindDN = strings.ToLower(bindDN)
rid := uuid.New().String()
req := SearchRequest{
SearchRequest: searchReq,
BindDN: bindDN,

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2021.8.1-rc1
version: 2021.8.1
description: Making authentication simple.
contact:
email: hello@beryju.org
@ -1710,7 +1710,7 @@ paths:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconRequest'
$ref: '#/components/schemas/FileUploadRequest'
security:
- authentik: []
- cookieAuth: []
@ -1738,13 +1738,13 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
required: true
security:
- authentik: []
@ -2515,6 +2515,7 @@ paths:
type: string
enum:
- api
- app_password
- recovery
- verification
- name: ordering
@ -3281,6 +3282,38 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/users/service_account/:
post:
operationId: core_users_service_account_create
description: Create a new user account that is marked as a service account
tags:
- core
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserServiceAccountRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/UserServiceAccountRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/UserServiceAccountRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/UserServiceAccountResponse'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/users/update_self/:
put:
operationId: core_users_update_self_update
@ -5459,7 +5492,7 @@ paths:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconRequest'
$ref: '#/components/schemas/FileUploadRequest'
security:
- authentik: []
- cookieAuth: []
@ -5487,13 +5520,13 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
$ref: '#/components/schemas/FilePathRequest'
required: true
security:
- authentik: []
@ -5580,7 +5613,7 @@ paths:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconRequest'
$ref: '#/components/schemas/FileUploadRequest'
security:
- authentik: []
- cookieAuth: []
@ -20381,7 +20414,8 @@ components:
- url
BackendsEnum:
enum:
- django.contrib.auth.backends.ModelBackend
- authentik.core.auth.InbuiltBackend
- authentik.core.auth.TokenBackend
- authentik.sources.ldap.auth.LDAPBackend
type: string
BindingTypeEnum:
@ -21549,6 +21583,24 @@ components:
type: string
required:
- expression
FilePathRequest:
type: object
description: Serializer to upload file
properties:
url:
type: string
required:
- url
FileUploadRequest:
type: object
description: Serializer to upload file
properties:
file:
type: string
format: binary
clear:
type: boolean
default: false
Flow:
type: object
description: Flow Serializer
@ -22211,6 +22263,7 @@ components:
- verification
- api
- recovery
- app_password
type: string
InvalidResponseActionEnum:
enum:
@ -27841,6 +27894,8 @@ components:
intent:
$ref: '#/components/schemas/IntentEnum'
user:
type: integer
user_obj:
$ref: '#/components/schemas/UserRequest'
description:
type: string
@ -29545,22 +29600,6 @@ components:
$ref: '#/components/schemas/UserSelf'
required:
- user
SetIconRequest:
type: object
properties:
file:
type: string
format: binary
clear:
type: boolean
default: false
SetIconURLRequest:
type: object
properties:
url:
type: string
required:
- url
SeverityEnum:
enum:
- notice
@ -29819,6 +29858,11 @@ components:
type: object
description: Get system information.
properties:
env:
type: object
additionalProperties:
type: string
readOnly: true
http_headers:
type: object
additionalProperties:
@ -29858,6 +29902,7 @@ components:
readOnly: true
required:
- embedded_outpost_host
- env
- http_headers
- http_host
- http_is_secure
@ -30014,6 +30059,8 @@ components:
intent:
$ref: '#/components/schemas/IntentEnum'
user:
type: integer
user_obj:
$ref: '#/components/schemas/User'
description:
type: string
@ -30044,6 +30091,8 @@ components:
intent:
$ref: '#/components/schemas/IntentEnum'
user:
type: integer
user_obj:
$ref: '#/components/schemas/UserRequest'
description:
type: string
@ -30533,6 +30582,26 @@ components:
required:
- name
- username
UserServiceAccountRequest:
type: object
properties:
name:
type: string
create_group:
type: boolean
default: false
required:
- name
UserServiceAccountResponse:
type: object
properties:
username:
type: string
token:
type: string
required:
- token
- username
UserSetting:
type: object
description: Serializer for User settings for stages and sources

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