Compare commits

...

283 Commits

Author SHA1 Message Date
bfe27d5979 web: Update Web API Client version (#1643)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-19 16:01:03 +02:00
3b7e8e3931 website/docs: fix typos
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-19 15:45:15 +02:00
03369e2338 sources/ldap: check for existence of vendor fields before falling back
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1521
2021-10-19 15:40:40 +02:00
5da7d9a573 release: 2021.10.1-rc1 2021-10-19 15:34:59 +02:00
12110e264d ci: remove pwgen
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-19 15:34:54 +02:00
f5049d3d0f build(deps): bump @typescript-eslint/eslint-plugin in /web (#1639) 2021-10-19 06:59:35 +02:00
b616253444 build(deps): bump flowchart.js from 1.15.0 to 1.16.0 in /web (#1640) 2021-10-19 06:58:04 +02:00
41efe49d27 build(deps): bump @typescript-eslint/parser from 5.0.0 to 5.1.0 in /web (#1641) 2021-10-19 06:57:55 +02:00
86d0e6ce45 build(deps): bump boto3 from 1.18.63 to 1.18.64 (#1642) 2021-10-19 06:57:35 +02:00
89bb27b95c sources/ldap: fix missing arguments?
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 22:04:58 +02:00
9333ffd04f website/docs: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 21:48:10 +02:00
2b155964c2 sources/ldap: extract vendor-specific functions
#1521

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 21:44:10 +02:00
c3bd509eb8 website/docs: add matrix docs
closes #1477

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 21:09:26 +02:00
72c0da2bdf build(deps): bump webauthn from 1.0.0 to 1.0.1 (#1638)
Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Changelog](https://github.com/duo-labs/py_webauthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: webauthn
  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-10-18 18:04:31 +02:00
151c62733f build(deps): bump goauthentik.io/api from 0.202198.3 to 0.202198.6 (#1637)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202198.3 to 0.202198.6.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202198.3...v0.202198.6)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 18:04:20 +02:00
dbdea24290 website: remove .git suffix for go import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 17:48:13 +02:00
909c4217bc website/docs: prepare 2021.10
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 17:06:18 +02:00
922fc9b8d5 sources/oauth: add Sign in with Apple (#1635)
* sources/oauth: add apple sign in support

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

* website/docs: apple sign in docs

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

* website/docs: fix missing apple in sidebar

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

* sources/oauth: add fallback values for name and slug

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 16:35:12 +02:00
2c06eed8e7 events: don't prefill task if they already have a state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 14:48:14 +02:00
a1b3af401d outposts: improve handling of recreate scenarios
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 14:29:37 +02:00
92d38f62b5 outposts: handle k8s 422 response code by recreating objects
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 10:23:11 +02:00
98a56c77e3 providers/proxy: update ingress controller to work with k8s 1.22
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-18 10:00:24 +02:00
e5906a4115 build(deps): bump @docusaurus/plugin-client-redirects in /website (#1629)
Bumps [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) from 2.0.0-beta.6 to 2.0.0-beta.7.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.7/packages/docusaurus-plugin-client-redirects)

---
updated-dependencies:
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 09:16:44 +02:00
20c6874bb4 build(deps): bump @rollup/plugin-typescript from 8.2.5 to 8.3.0 in /web (#1630)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins/tree/HEAD/packages/typescript) from 8.2.5 to 8.3.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/typescript/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/typescript-v8.3.0/packages/typescript)

---
updated-dependencies:
- dependency-name: "@rollup/plugin-typescript"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 09:15:35 +02:00
222d3bd358 build(deps): bump @docusaurus/preset-classic in /website (#1631)
Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 2.0.0-beta.6 to 2.0.0-beta.7.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v2.0.0-beta.7/packages/docusaurus-preset-classic)

---
updated-dependencies:
- dependency-name: "@docusaurus/preset-classic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 09:15:26 +02:00
02c15f7c43 build(deps): bump boto3 from 1.18.62 to 1.18.63 (#1632)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.62 to 1.18.63.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.62...1.18.63)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 09:15:16 +02:00
ab200eb855 build(deps): bump django-storages from 1.12.1 to 1.12.2 (#1633)
Bumps [django-storages](https://github.com/jschneier/django-storages) from 1.12.1 to 1.12.2.
- [Release notes](https://github.com/jschneier/django-storages/releases)
- [Changelog](https://github.com/jschneier/django-storages/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jschneier/django-storages/compare/1.12.1...1.12.2)

---
updated-dependencies:
- dependency-name: django-storages
  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-10-18 09:15:06 +02:00
9e8ce012e3 build(deps): bump pyjwt from 2.2.0 to 2.3.0 (#1634)
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-18 09:14:55 +02:00
00dc8f8b1f ci: backoff translation compile ci
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-17 22:15:38 +02:00
ce812e14c7 core: improve detection for s3 settings to trigger backup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-17 15:56:31 +02:00
8d32a53126 outposts: add additional error checking for docker controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-17 15:54:57 +02:00
f9b6b1dd3f web/admin: improve visibility of oauth rsa key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-17 14:15:36 +02:00
9679be39fa lifecycle: bump celery healthcheck to 5s timeout
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1627
2021-10-16 14:28:05 +02:00
0225bf9c99 stages/authenticator_validate: create a default authenticator validate stage with sensible defaults
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-16 00:28:56 +02:00
8040e2b6e4 build(deps): bump webauthn from 0.4.7 to 1.0.0 (#1625)
* build(deps): bump webauthn from 0.4.7 to 1.0.0

Bumps [webauthn](https://github.com/duo-labs/py_webauthn) from 0.4.7 to 1.0.0.
- [Release notes](https://github.com/duo-labs/py_webauthn/releases)
- [Commits](https://github.com/duo-labs/py_webauthn/compare/v0.4.7...v1.0.0)

---
updated-dependencies:
- dependency-name: webauthn
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* stages/authenticator_webauthn: migrate to new library version

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

* stages/authenticator_validate: migrate to new version

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

* stages/authenticator_webauthn: add bytes_to_base64url_dict for json encoding

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

* actually don't do that

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

* fix missing response on web

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

* more double json

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

* fix

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

* more base64 stuff

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

* working

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

* ci: always sync

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

* fix

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-10-15 23:26:29 +02:00
56a56ffdbf web: new default flow background
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-15 23:03:41 +02:00
afedcc0074 build(deps): bump drf-spectacular from 0.20.1 to 0.20.2 (#1624)
Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/tfranzel/drf-spectacular/releases)
- [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.20.1...0.20.2)

---
updated-dependencies:
- dependency-name: drf-spectacular
  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-10-15 17:48:51 +00:00
4d93e30147 web: Update Web API Client version (#1623)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-15 19:29:44 +02:00
f62786e58b policies: add additional filters to create flow charts on frontend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-15 18:21:44 +02:00
f76c1a6f93 build(deps): bump @patternfly/patternfly from 4.135.2 to 4.144.5 in /web (#1621) 2021-10-15 08:33:46 +02:00
56871523e7 build(deps): bump boto3 from 1.18.61 to 1.18.62 (#1622) 2021-10-15 08:33:31 +02:00
5f9dda2e58 outposts: rename docker_image_base to container_image_base, since its not docker specific
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 20:28:30 +02:00
0c55eea678 outposts: Adding more flexibility to outposts in Kubernetes. (#1617)
* outposts/ldap: Support hard coded `uidNumber` and `gidNumber`.

* outposts: more options for image + labels

- Set outpost docker image in config.
- Set image pull secrets in outpost config.
- Add additional labels for easier targeting from
  custom services.

* Fix some linting errors.

* outposts: Rename `docker_image` to `container_image
2021-10-14 19:54:56 +02:00
19a343dadb web: fix linting on rollup config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 19:49:31 +02:00
3ab9798f38 web: prepare for building with external API bases
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 19:45:20 +02:00
dd9dc7e596 root: fix error with sentry proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 19:45:01 +02:00
797e31696a outposts: fix attribute error in docker controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 13:37:56 +02:00
9a42c5815d web/admin: add fallback font for doughnut charts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 12:50:52 +02:00
f341479732 web: make table pagination size user-configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 12:48:52 +02:00
8eddb4b95b admin: check for debug in worker count api
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 12:32:30 +02:00
5c58532121 web/admin: default to warning state for backup task
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 12:31:01 +02:00
4b7399f454 *: add @prefill_task() decorator to "pre-fill" tasks in cache, so they can be executed even before their schedule would do so
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 12:21:28 +02:00
27982a771c web: Update Web API Client version (#1620)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-14 11:52:50 +02:00
8296d0c94c web/admin: fix SMS Authenticator stage not loading state correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 11:49:55 +02:00
9bc9568008 stages/authenticator_sms: make fields non-nullable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 11:42:11 +02:00
07d619d257 website/docs: add authenticator_sms stage docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 11:33:46 +02:00
6ee7d5bf9c web: Update Web API Client version
Signed-off-by: GitHub <noreply@github.com>
2021-10-14 10:25:13 +02:00
634375c43f stages/authenticator_sms: add generic provider (#1595)
* stages/sms: New SMS provider, aka wrapper for outside API

* web/pages/authenicator_sms: Conditionally show options based on provider.

* stages/authenicator_sms: Fixing up the model.

* Whoops

* stages/authenicator_sms: Adding supported auth types for Generic provider.

* web/pages/stages/authenicator_sms: Added auth type for generic provider

* web/pages/stages/authenicator_sms: Fixing up my generic provider options.

* stages/authenicator/sms: Working version of generic provider.

* stages/authenicator/sms: Cleanup and creating an event on error.

* web/ages/stages/authenicator_sms: Made a default for Auth Type and cleaned up the non-needed name attribute.

* stages/authenicator_validate: Fixing up the migration as it had no SMS.

* stages/authenicator_sms: Removd non-needed migration and better error code handling.

* stages/authenicator_sms: Removd non-needed migration and better error code handling.

* web/pages/stages/authenicator_sms: Provider default is not empty anymore.

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-14 10:24:15 +02:00
10fc33f7d3 build(deps): bump eslint from 8.0.0 to 8.0.1 in /web (#1612) 2021-10-14 08:33:55 +02:00
ee140014e9 build(deps): bump pyyaml from 5.4.1 to 6.0 (#1613) 2021-10-14 08:33:07 +02:00
2d363948b6 build(deps-dev): bump selenium from 3.141.0 to 4.0.0 (#1614) 2021-10-14 08:32:50 +02:00
dcb3ef14d1 build(deps): bump boto3 from 1.18.60 to 1.18.61 (#1615) 2021-10-14 08:32:37 +02:00
a71ef7f36c build(deps): bump django-model-utils from 4.1.1 to 4.2.0 (#1616) 2021-10-14 08:32:28 +02:00
4d51ec906d internal/proxyv2: improve error handling when configuring app
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 21:48:11 +02:00
cd42281383 Revert "website: use 302-based proxy for goauthentik.io/v2"
This reverts commit faf706cbec.
2021-10-13 21:27:26 +02:00
faf706cbec website: use 302-based proxy for goauthentik.io/v2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 21:25:12 +02:00
16c05a7bbc tests: migrate to selenium 4 (#1611) 2021-10-13 19:06:19 +02:00
2ad5995332 website/docs: add symlink for latest compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 17:51:54 +02:00
f73a404fd6 website: make static files available under domain
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 17:45:45 +02:00
178e8e7e43 web/user: initial optimisation for smaller screens
see #1610

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 11:23:26 +02:00
98907ec889 root: remove structlog.processors.format_exc_info for new structlog version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-13 09:42:49 +02:00
9dd9ab6da3 build(deps): bump structlog from 21.1.0 to 21.2.0 (#1609)
Bumps [structlog](https://github.com/hynek/structlog) from 21.1.0 to 21.2.0.
- [Release notes](https://github.com/hynek/structlog/releases)
- [Changelog](https://github.com/hynek/structlog/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/hynek/structlog/compare/21.1.0...21.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-13 09:41:44 +02:00
80c6b8f0c7 build(deps): bump @typescript-eslint/parser from 4.33.0 to 5.0.0 in /web (#1604) 2021-10-13 08:36:09 +02:00
8436814874 build(deps): bump codemirror from 5.63.1 to 5.63.3 in /web (#1606) 2021-10-13 08:34:56 +02:00
3c16bdce45 build(deps): bump typescript from 4.4.3 to 4.4.4 in /web (#1607) 2021-10-13 08:34:34 +02:00
a2bce79796 build(deps): bump boto3 from 1.18.59 to 1.18.60 (#1608) 2021-10-13 08:34:24 +02:00
3e5b05203b Revert "root: handle liveness probe in router"
This reverts commit d39dbc7287.

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 18:44:37 +02:00
57e86582d1 Revert "root: handle liveness probe in router (also keep internal one)"
This reverts commit dd7cb45733.
2021-10-12 18:44:08 +02:00
dd7cb45733 root: handle liveness probe in router (also keep internal one)
This reverts commit d39dbc7287.

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 18:43:39 +02:00
2b09d97522 core: fix squash migrations error when AK_ADMIN_TOKEN is set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 17:45:10 +02:00
d39dbc7287 root: handle liveness probe in router
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 14:54:15 +02:00
48f96ea55f lifecycle: only set prometheus_multiproc_dir in ak wrapper to prevent full disk on worker
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 14:44:32 +02:00
22a7c25526 internal: call GetStore on application to improve logging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 13:33:20 +02:00
cc69311ec0 stages/authenticator_validate: add new class
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 13:13:31 +02:00
15d7004e25 tests/e2e: use vanity urls
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 12:38:23 +02:00
ddb70a283e managed: don't run managed reconciler in foreground on startup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 12:10:46 +02:00
ecfc3a6d93 *: migrate everything to goauthentik.io docker proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 11:04:47 +02:00
5753182e03 root: migrate docker images to netlify proxy (#1603)
* migrate to netlify proxy

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

* relative forward to func

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

* custom logic for go paths

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

* fix const

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

* missing break

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

* add default for new repos

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-12 10:58:02 +02:00
db79244ba4 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1602)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.33.0 to 5.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-12 09:26:39 +02:00
3231bcea66 build(deps): bump eslint from 7.32.0 to 8.0.0 in /web (#1596) 2021-10-12 08:34:10 +02:00
5e0299ca82 build(deps): bump @types/codemirror from 5.60.4 to 5.60.5 in /web (#1597) 2021-10-12 08:33:01 +02:00
42e35aace0 build(deps-dev): bump coverage from 6.0.1 to 6.0.2 (#1598) 2021-10-12 08:32:23 +02:00
d96cfc8e30 build(deps): bump goauthentik.io/api from 0.202198.2 to 0.202198.3 (#1599) 2021-10-12 08:32:13 +02:00
36c97afc44 build(deps): bump django-storages from 1.12 to 1.12.1 (#1600) 2021-10-12 08:32:02 +02:00
9c322be8d7 build(deps): bump boto3 from 1.18.58 to 1.18.59 (#1601) 2021-10-12 08:31:51 +02:00
cf09205933 website/docs: fix sidebar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 22:45:24 +02:00
e851a7f294 website/docs: add missing make migrate
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 22:33:31 +02:00
e4f141c6c0 *: Squash Migrations (#1593)
* *: first squash pass

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

* sources/saml: squash less

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

* outposts: fix docker controller not correctly checking image

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

* tests/e2e: fix old migration reference

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 21:39:35 +02:00
35fa93d9aa ci: fix missing gettext
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 17:59:52 +02:00
2bdc0102f9 web: Update Web API Client version (#1592)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-11 17:53:43 +02:00
aef9d27706 stages/authenticator_sms: Add SMS Authenticator Stage (#1577)
* stages/authenticator_sms: initial implementation

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

* web/admin: add initial stage UI

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

* web/elements: clear invalid state when old input was invalid but new input is correct

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

* stages/authenticator_sms: add more logic

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

* web/user: add basic SMS settings

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

* stages/authenticator_sms: initial working version

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

* stages/authenticator_sms: add tests

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

* web/flows: optimise totp password manager entry on authenticator_validation stage

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

* web/elements: add grouping support for table

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

* web/admin: allow sms class in authenticator stage

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

* web/admin: add grouping to more pages

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

* stages/authenticator_validate: add SMS support

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

* api: add throttling for flow executor based on session key and pending user

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

* web: fix style issues

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

* ci: add workflow to compile backend translations

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 17:51:49 +02:00
7bf587af24 ci: push dev images to ghcr (#1591)
* ci: push dev images to ghcr

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

* *: use new ghcr images

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

* website/docs: use ghcr proxy

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 14:08:34 +02:00
ef1cf7867c Revert "build(deps): bump eslint from 7.32.0 to 8.0.0 in /web (#1584)"
This reverts commit e22b8f5fdc.
2021-10-11 13:43:36 +02:00
da443b443c website: use ghcr proxy for registry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-11 10:08:21 +02:00
f4322e665a build(deps): bump github.com/go-openapi/strfmt from 0.20.2 to 0.20.3 (#1585)
Bumps [github.com/go-openapi/strfmt](https://github.com/go-openapi/strfmt) from 0.20.2 to 0.20.3.
- [Release notes](https://github.com/go-openapi/strfmt/releases)
- [Commits](https://github.com/go-openapi/strfmt/compare/v0.20.2...v0.20.3)

---
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-10-11 09:20:36 +02:00
e22b8f5fdc build(deps): bump eslint from 7.32.0 to 8.0.0 in /web (#1584) 2021-10-11 08:33:38 +02:00
a18176af56 build(deps): bump react-before-after-slider-component in /website (#1583) 2021-10-11 08:32:48 +02:00
4132fd139c build(deps): bump goauthentik.io/api from 0.202197.2 to 0.202198.2 (#1586) 2021-10-11 08:32:28 +02:00
b077bb8783 build(deps): bump github.com/go-openapi/runtime from 0.19.31 to 0.20.0 (#1587) 2021-10-11 08:32:20 +02:00
69665d9547 build(deps): bump pycryptodome from 3.10.4 to 3.11.0 (#1588) 2021-10-11 08:32:11 +02:00
d0f056357d build(deps): bump boto3 from 1.18.57 to 1.18.58 (#1589) 2021-10-11 08:32:02 +02:00
9ed236f7ab outposts/ldap: Support hard coded uidNumber and gidNumber. (#1582) 2021-10-10 23:43:36 +02:00
83f4830946 website: add netlify config file
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-10 22:26:33 +02:00
e23df99a9e web: Update Web API Client version (#1580)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-10 18:57:23 +02:00
b80ecd4668 stages/prompt: fix wrong field type of field_key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-10 18:54:56 +02:00
66ca488ea0 web: Update Web API Client version (#1579)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-10 14:43:58 +02:00
d959b7a930 Merge branch 'version-2021.9' 2021-10-10 14:35:40 +02:00
62ae3f1e31 website/docs: prepare 2021.9.8
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-10 13:22:36 +02:00
619203c177 release: 2021.9.8 2021-10-10 13:12:26 +02:00
a1adf382af root: don't compilemessages in build process since it requires redis, do it on startup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 23:38:25 +02:00
834bddd0da root: add AUTHENTIK_SECRET_KEY for compilemessages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 22:52:32 +02:00
7d9251ce2f root: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 20:56:49 +02:00
fb13a46252 web: Update Web API Client version (#1576)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-09 20:23:38 +02:00
dfefdbfd7c root: compile messages for backend in docker
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 20:11:51 +02:00
846c971674 root: add translation for backend strings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 20:07:28 +02:00
5b7e1f97e0 stages/authenticator_duo: remove signals
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 19:29:35 +02:00
dff0613b3d crypto: add managed field, prepare managed JWT cert
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 19:14:39 +02:00
0a4343d61b web: Update Web API Client version (#1575)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-09 16:05:02 +02:00
09696207a6 web/user: remove debug
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 16:03:14 +02:00
8965451073 core: add default for user's settings attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 16:01:22 +02:00
994c1c4b6a web: Update Web API Client version (#1574)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-09 16:01:14 +02:00
3ee5a672f1 web/user: load interface settings from user settings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 15:52:36 +02:00
b33ea9cc61 core: add settings serializer to user/me and update_self endpoints, saved in a key in attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-09 15:35:18 +02:00
50a623d8ab build(deps): bump golang from 1.17.1 to 1.17.2 (#1566) 2021-10-08 08:34:37 +02:00
cdbf7ae567 build(deps): bump lit from 2.0.0 to 2.0.2 in /web (#1567) 2021-10-08 08:34:21 +02:00
1307a39042 build(deps): bump react-before-after-slider-component in /website (#1569) 2021-10-08 08:33:54 +02:00
dca34cfbd3 build(deps): bump docker from 5.0.2 to 5.0.3 (#1571) 2021-10-08 08:33:39 +02:00
735f7cbd69 build(deps): bump boto3 from 1.18.56 to 1.18.57 (#1570) 2021-10-08 08:33:20 +02:00
728356d420 build(deps): bump goauthentik.io/api from 0.202197.1 to 0.202197.2 (#1572) 2021-10-08 08:32:54 +02:00
a9f6f1563d ci: fix more order
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 22:40:13 +02:00
155c28d7cd ci: prepare variables before checking out stable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 22:40:13 +02:00
f9a180eb1f ci: fix gh_env
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 22:40:13 +02:00
4ae476e58d Revert "build(deps): bump construct-style-sheets-polyfill in /web (#1531)"
This reverts commit 55259adf38.
2021-10-07 22:35:41 +02:00
f32d35b07c policies/password: add extra sub_text field in tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 19:27:24 +02:00
9e936e4436 outposts: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 19:15:57 +02:00
649abddea7 outposts: fallback to known-good outpost image if configured image cannot be pulled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 19:10:39 +02:00
956382b682 ci: set separate variable for container branch name
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 19:06:03 +02:00
67b88595ad stages/prompt: fix sub_text not allowing blank
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 19:00:15 +02:00
b4ee693a5c stages/user_write: allow recursive writing to user.attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 18:57:19 +02:00
57e5acaf2f stages/prompt: add sub_text field to add HTML below prompt fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 18:34:37 +02:00
050ec99c89 flows: fix inspector history not being cleared when executing from API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 18:29:28 +02:00
10fd1c8120 web/admin: truncate prompt label when too long
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 17:58:07 +02:00
070745e764 Revert "build(deps): bump construct-style-sheets-polyfill in /web (#1531)"
This reverts commit 55259adf38.
2021-10-07 10:48:12 +02:00
cbeee27fc1 build(deps): bump @sentry/tracing from 6.13.2 to 6.13.3 in /web (#1556)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-07 09:39:44 +02:00
2bc4d0cedb build(deps): bump @babel/core from 7.15.5 to 7.15.8 in /web (#1555) 2021-10-07 08:28:13 +02:00
5105a1c207 build(deps): bump @babel/plugin-transform-runtime in /web (#1557) 2021-10-07 08:28:02 +02:00
64e357ab0e build(deps): bump @babel/preset-env from 7.15.6 to 7.15.8 in /web (#1554) 2021-10-07 08:27:03 +02:00
6ca93525aa build(deps): bump @babel/plugin-proposal-decorators in /web (#1558) 2021-10-07 08:26:21 +02:00
a2c978768c build(deps): bump @sentry/browser from 6.13.2 to 6.13.3 in /web (#1559) 2021-10-07 08:26:11 +02:00
f0c7be7144 build(deps): bump pyjwt from 2.1.0 to 2.2.0 (#1560) 2021-10-07 08:26:00 +02:00
0f96e3e4b3 build(deps): bump django-storages from 1.11.1 to 1.12 (#1561) 2021-10-07 08:25:44 +02:00
d42fc37a88 build(deps): bump goauthentik.io/api from 0.202196.1 to 0.202197.1 (#1562) 2021-10-07 08:25:16 +02:00
4ecd8f5dcf build(deps): bump boto3 from 1.18.55 to 1.18.56 (#1563) 2021-10-07 08:25:06 +02:00
d7a194b512 build(deps-dev): bump coverage from 6.0 to 6.0.1 (#1564) 2021-10-07 08:24:56 +02:00
753f8d38bf web: Update Web API Client version (#1552)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-06 21:09:30 +02:00
118a54517a website/docs: add 2021.9.7 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-06 21:05:04 +02:00
8c27616d0c Merge branch 'version-2021.9' 2021-10-06 21:04:16 +02:00
097a42bb7b web/admin: fix description for flow import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-06 20:50:43 +02:00
26f1f47cc1 Revert "build(deps): bump python from 3.9-slim-buster to 3.10.0-slim-buster (#1546)"
This reverts commit 471f9c6d05.
2021-10-06 09:55:44 +02:00
471f9c6d05 build(deps): bump python from 3.9-slim-buster to 3.10.0-slim-buster (#1546)
Bumps python from 3.9-slim-buster to 3.10.0-slim-buster.

---
updated-dependencies:
- dependency-name: python
  dependency-type: direct:production
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-06 09:35:01 +02:00
67d13f19a1 root: fix syntax error in dockerfile healthcheck
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-06 09:18:01 +02:00
1b7c19cf50 build(deps): bump eslint-plugin-lit from 1.5.1 to 1.6.0 in /web (#1547) 2021-10-06 08:29:03 +02:00
b012ae600d build(deps): bump boto3 from 1.18.54 to 1.18.55 (#1548) 2021-10-06 08:28:33 +02:00
1838101d60 build(deps): bump goauthentik.io/api from 0.202195.4 to 0.202196.1 (#1549) 2021-10-06 08:28:23 +02:00
929add4e9c build(deps): bump django from 3.2.7 to 3.2.8 (#1550) 2021-10-06 08:28:15 +02:00
18edaea658 website/docs: fix header of release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-06 00:08:03 +02:00
8030e45d75 web: Update Web API Client version (#1545)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-06 00:07:00 +02:00
d75c63d38b Merge branch 'version-2021.9'
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/locales/fr_FR.po
2021-10-06 00:04:09 +02:00
52889ffea1 website/docs: add 2021.9.6 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-06 00:03:29 +02:00
6c603cdf80 internal: add internal healthchecking to prevent websocket errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 22:21:14 +02:00
5f4a1417b2 cmd: prevent outposts from panicking when failing to get their config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 22:19:05 +02:00
e8420957b1 lifecycle: fix syntax error in ak wrapper
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 21:03:54 +02:00
aee58c8d53 root: add docker-native healthcheck for web and celery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:45:18 +02:00
c47ab4f1fc root: remove redundant internal network from compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:39:27 +02:00
fa6df84de2 admin: clear update notification when notification's version matches current version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:36:38 +02:00
1faa403fe2 root: update security
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:08:26 +02:00
7f5feb9451 web: fix strings not being translated at all when matching browser locale not found
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 16:23:40 +02:00
b85aeae5ef web: ensure fallback locale is loaded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 14:53:02 +02:00
b72b731320 build(deps): bump goauthentik.io/api from 0.202195.3 to 0.202195.4 (#1544)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202195.3 to 0.202195.4.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202195.3...v0.202195.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-05 13:19:20 +02:00
65de4b8cad web: Update Web API Client version (#1543)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-05 13:12:42 +02:00
9e7e22367b core: include group uuids in self serializer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:10:44 +02:00
9301b27e43 build(deps): bump goauthentik.io/api from 0.202195.1 to 0.202195.3 (#1542)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202195.1 to 0.202195.3.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202195.1...v0.202195.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-05 13:00:06 +02:00
7b415a24ee web: Update Web API Client version (#1540)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-05 12:33:50 +02:00
f5761dc70d core: only return group names for user_self
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 12:31:25 +02:00
4f57dfda93 web/flows: adjust message for email stage
closes #1538

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:28:11 +02:00
16380b3f7a api: ensure viewsets have default ordering
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:26:41 +02:00
b0e416e9f0 web/elements: fix model form always loading when viewport check is disabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:23:45 +02:00
16f2603130 core: make user's name field fully options
closes #1537

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:23:27 +02:00
e742494f3d web: Update Web API Client version (#1539)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-05 11:14:04 +02:00
5fdca722f4 web/admin: only show outpost deployment info when not embedded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:11:44 +02:00
847cfed73f web/user: don't show managed tokens in user interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:11:34 +02:00
19247accd9 web: fix rendering of token copy button in dark mode
closes #1528

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 11:11:13 +02:00
05b587ae44 outposts: fix error when comparing ports in docker controller when port mapping is disabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 10:44:07 +02:00
a515afae0b recovery: handle error when user doesn't exist
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 10:40:59 +02:00
8da00585e3 Translate /web/src/locales/en.po in fr_FR (#1536)
translation completed for the source file '/web/src/locales/en.po'
on the 'fr_FR' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2021-10-05 10:30:01 +02:00
b70a72f247 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1529) 2021-10-05 07:19:47 +02:00
11160b6e04 build(deps): bump postcss from 8.3.8 to 8.3.9 in /website (#1530) 2021-10-05 07:18:40 +02:00
55259adf38 build(deps): bump construct-style-sheets-polyfill in /web (#1531) 2021-10-05 07:18:28 +02:00
3f308ad48c build(deps): bump @typescript-eslint/parser in /web (#1532) 2021-10-05 07:18:20 +02:00
ee6fd6f609 build(deps): bump react-before-after-slider-component in /website (#1533) 2021-10-05 07:17:38 +02:00
d53d0c353f build(deps): bump goauthentik.io/api from 0.202194.1 to 0.202195.1 (#1534) 2021-10-05 07:17:25 +02:00
1360b76d1b build(deps): bump boto3 from 1.18.53 to 1.18.54 (#1535) 2021-10-05 07:17:13 +02:00
e22a286a6f lifecycle: only lock database when system migrations need to be applied, and during django migrations, and don't double unlock
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 23:14:16 +02:00
62c0f69541 web: Update Web API Client version (#1527)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-04 22:07:00 +02:00
1c340ddbbd Merge branch 'version-2021.9'
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/package-lock.json
#	web/package.json
2021-10-04 22:02:56 +02:00
62af5b2dd3 website/docs: add 2021.9.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 19:33:11 +02:00
e0859686c4 web: Update Web API Client version (#1526)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-04 18:50:43 +02:00
0692663537 stages/email: add activate_user_on_success flag, add for all example flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:47:51 +02:00
b5649bdcc4 stages/user_login: add check for user.is_active and tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:37:05 +02:00
418e491799 Revert "build(deps): bump construct-style-sheets-polyfill in /web (#1516)"
This reverts commit 0c6237d8c4.
2021-10-04 18:22:21 +02:00
fab9a10487 outposts: don't always build permissions on outpost.user access, only in signals and tasks
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:04:19 +02:00
9778050dda lifecycle: switch to h11 uvicorn worker for now
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:03:08 +02:00
9ac808ee98 website/docs: add missing pipenv instructions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 17:54:06 +02:00
0f00b27384 events: add missing migration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 17:51:31 +02:00
ab5981836d providers/ldap: use RDN when using posixGroup's memberUid attribute (#1514)
Use the RDN instead of the FDN when establishing group memberships based on posixGroup's 'memberUid' attribute.

fixes #1436

Signed-off-by: Steven Armstrong <steven@armstrong.cc>
2021-10-04 10:56:06 +02:00
a4418a83f8 web/admin: fix search group label
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 09:41:16 +02:00
36b23c4624 root: coverage with toml support
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 09:17:31 +02:00
0c6237d8c4 build(deps): bump construct-style-sheets-polyfill in /web (#1516)
Bumps [construct-style-sheets-polyfill](https://github.com/calebdwilliams/adoptedStyleSheets) from 2.4.17 to 3.0.3.
- [Release notes](https://github.com/calebdwilliams/adoptedStyleSheets/releases)
- [Changelog](https://github.com/calebdwilliams/construct-style-sheets/blob/main/CHANGELOG.md)
- [Commits](https://github.com/calebdwilliams/adoptedStyleSheets/commits/v3.0.3)

---
updated-dependencies:
- dependency-name: construct-style-sheets-polyfill
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 09:11:55 +02:00
e546453250 build(deps): bump react-before-after-slider-component in /website (#1517) 2021-10-04 08:33:28 +02:00
5b35d71bb3 build(deps): bump rollup from 2.57.0 to 2.58.0 in /web (#1518) 2021-10-04 08:33:03 +02:00
cddff85e1c build(deps): bump @types/codemirror from 5.60.3 to 5.60.4 in /web (#1519) 2021-10-04 08:32:34 +02:00
c65c6a62cc build(deps): bump boto3 from 1.18.52 to 1.18.53 (#1520) 2021-10-04 08:32:24 +02:00
1bc51adcac build(deps): bump goauthentik.io/api from 0.202193.3 to 0.202194.1 (#1522) 2021-10-04 08:32:11 +02:00
c523b799be build(deps-dev): bump coverage from 5.5 to 6.0 (#1524) 2021-10-04 08:32:00 +02:00
9d0d779f40 build(deps): bump drf-spectacular from 0.19.0 to 0.20.1 (#1523) 2021-10-04 08:31:17 +02:00
8a791c4eac tests/e2e: fallback to gh-master if outpost docker image cannot be found for PR
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 23:12:11 +02:00
036a4e86e2 tests/integration: fix tests failing due to incorrect comparison
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 22:54:07 +02:00
4715e7bf04 website/docs: fix description for docker outpost settings (#1513)
Changed the first "Kubernetes outpost specific settings" to Docker
2021-10-03 19:43:56 +02:00
83150d9920 outposts: fix circular import in kubernetes controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:25:18 +02:00
d30dcda814 providers/proxy: always check ingress secret in kubernetes controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:14:27 +02:00
c720c9f41b outposts: check ports of deployment in kubernetes outpost controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:09:52 +02:00
62cfb76b39 website/docs: fix order of steps on bookstack integration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 18:46:47 +02:00
1c52836060 web: fix package lock
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 01:17:41 +02:00
f3cc1be0f2 Translate /web/src/locales/en.po in fr_FR (#1509)
translation completed updated for the source file '/web/src/locales/en.po'
on the 'fr_FR' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2021-10-03 00:32:17 +02:00
8dd77793a0 sources/ldap: fix logic error in Active Directory account disabled status
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 00:30:35 +02:00
f6e8dbfb5e outposts/proxy: show full error message when user is authenticated
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-02 22:00:37 +02:00
3c1ac4c7ec outposts/proxy: add new headers with unified naming
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-02 22:00:23 +02:00
52bbf454e3 outpost/proxy: fix missing negation for internal host ssl verification
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-02 21:17:15 +02:00
1252c6b07d web: add locale detection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-01 14:11:54 +02:00
3493d35af9 web: Translate /web/src/locales/en.po in fr_FR (#1506)
translated updated for the source file '/web/src/locales/en.po'
on the 'fr_FR' language.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-01 14:02:18 +02:00
f8e4ffbc85 web: Update Web API Client version (#1505)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-10-01 12:24:12 +02:00
faca127217 Merge branch 'version-2021.9'
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	Pipfile.lock
2021-10-01 12:19:11 +02:00
f88575cec4 website/docs: prepare 2021.9.4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-01 10:09:54 +02:00
b4eac771c2 build(deps): bump boto3 from 1.18.51 to 1.18.52 (#1503) 2021-10-01 08:49:09 +02:00
84e4ec4406 build(deps): bump channels-redis from 3.3.0 to 3.3.1 (#1504) 2021-10-01 08:48:59 +02:00
53e15bfbca sources/ldap: add support for Active Directory userAccountControl attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:13:48 +02:00
8bce16e6b4 sources/ldap: don't sync ldap source when no property mappings are set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 18:49:18 +02:00
e9bb8c896b web/admin: fix LDAP Source form not exposing syncParentGroup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 18:10:56 +02:00
de5455716d build(deps): bump codemirror from 5.63.0 to 5.63.1 in /web (#1500) 2021-09-30 08:40:57 +02:00
1d879400f2 build(deps): bump boto3 from 1.18.49 to 1.18.51 (#1501) 2021-09-30 08:40:48 +02:00
5136ae17f5 build(deps): bump sentry-sdk from 1.4.2 to 1.4.3 (#1502) 2021-09-30 08:40:38 +02:00
4cb8ae760a outposts: allow disabling of docker controller port mapping
closes #1474

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 23:55:22 +02:00
e4898f4b92 web/elements: use dedicated button for search clear instead of webkit exclusive one
closes #1499

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 21:47:38 +02:00
a2f3c54c2a website/docs: use full paths for forward_auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 21:36:57 +02:00
c0a0b52fbb web/elements: fix initialLoad not being done when viewportCheck was disabled
closes #1497

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 21:36:44 +02:00
cfd4817bb5 root: Use fully qualified names for docker bases base images. (#1490)
Signed-off-by: Steven Armstrong <steven@armstrong.cc>
2021-09-29 09:52:07 +02:00
94ae52b576 web/admin: don't require username nor name for activate/deactivate toggles
closes #1491

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 09:42:59 +02:00
be479f2453 build(deps): bump @lingui/core from 3.11.1 to 3.12.1 in /web (#1493) 2021-09-29 09:10:30 +02:00
c5d066577d build(deps): bump @lingui/macro from 3.11.1 to 3.12.1 in /web (#1492) 2021-09-29 08:42:45 +02:00
9ec6eaf4b8 build(deps): bump @lingui/cli from 3.11.1 to 3.12.1 in /web (#1494) 2021-09-29 08:34:38 +02:00
b057120351 build(deps): bump goauthentik.io/api from 0.202193.1 to 0.202193.3 (#1495) 2021-09-29 08:34:27 +02:00
b8082598a1 web: fix linting again
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-28 09:49:22 +02:00
1b5a163f46 web: Update Web API Client version (#1489)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-28 09:40:54 +02:00
1f2f48a7bc web: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-28 09:39:21 +02:00
f9ad102915 flows: inspector (#1469)
* flows: add initial inspector

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

* flows: change naming a bit

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

* web/flow: add inspector frame

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

* core: don't use shadydom when inspecting

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

* flows: add current stage to api

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

* stages/*: fix imports

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

* flows: deep-copy plan instead of just adding

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

* web/flows: ui

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

* flows: restrict inspector to admin

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

* web/admin: add buttons to launch flow with inspector

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

* web/flows: don't automatically follow redirects when inspector is open

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

* flows: make current_plan optional, only require historry

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

* web/flows: handle error messages in inspector

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

* web/flows: improve UI when flow is done

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

* flows: add is_completed flag to inspector

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

* flows: fix monkeypatches for tests

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

* flows: add inspector tests

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

* ci: re-enable cache

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-28 09:36:48 +02:00
ea4b920264 build(deps): bump @typescript-eslint/parser in /web (#1484) 2021-09-28 09:08:39 +02:00
7d8390ca77 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1485) 2021-09-28 08:32:35 +02:00
7ae551da65 build(deps): bump boto3 from 1.18.48 to 1.18.49 (#1486) 2021-09-28 08:32:25 +02:00
51b26c2ac7 build(deps): bump goauthentik.io/api from 0.202192.5 to 0.202193.1 (#1487) 2021-09-28 08:32:10 +02:00
e4a5f22f9b build(deps): bump sentry-sdk from 1.4.1 to 1.4.2 (#1488) 2021-09-28 08:31:57 +02:00
2462d58135 outposts/proxy: fix duplicate protocol in domain auth mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 20:49:00 +02:00
44534153a0 website/docs: fix hardcoded name in service-account yaml
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 20:18:12 +02:00
facfea035b web: Update Web API Client version (#1483) 2021-09-27 18:52:42 +02:00
323 changed files with 14318 additions and 4591 deletions

View File

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

View File

@ -1 +1,3 @@
keypair
keypairs
hass

View File

@ -25,14 +25,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run pylint
run: pipenv run pylint authentik tests lifecycle
@ -43,14 +43,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run black
run: pipenv run black --check authentik tests lifecycle
@ -61,14 +61,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run isort
run: pipenv run isort --check authentik tests lifecycle
@ -79,14 +79,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run bandit
run: pipenv run bandit -r authentik tests lifecycle
@ -113,14 +113,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations
run: pipenv run python -m lifecycle.migrate
@ -133,32 +133,39 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: prepare variables
id: ev
run: |
python ./scripts/gh_env.py
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: checkout stable
run: |
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
git checkout $(git describe --abbrev=0 --match 'version/*')
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
# Sync anyways since stable will have different dependencies
pipenv sync --dev
- name: run migrations to stable
run: pipenv run python -m lifecycle.migrate
- name: prepare variables
id: ev
run: |
python ./scripts/gh_do_set_branch.py
- name: checkout current code
run: |
set -x
git fetch
git checkout ${{ steps.ev.outputs.branchName }}
pipenv sync --dev
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: migrate to latest
run: pipenv run python -m lifecycle.migrate
test-unittest:
@ -168,14 +175,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
@ -197,14 +204,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
@ -236,17 +243,17 @@ jobs:
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/ci.docker-compose.yml up -d
docker-compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
uses: actions/cache@v2.1.6
with:
@ -290,20 +297,20 @@ jobs:
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
run: |
python ./scripts/gh_do_set_branch.py
python ./scripts/gh_env.py
- name: Login to Container Registry
uses: docker/login-action@v1
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: beryju.org
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building Docker Image
uses: docker/build-push-action@v2
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}
beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}
ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.sha }}
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}

View File

@ -48,22 +48,22 @@ jobs:
env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }}
run: |
python ./scripts/gh_do_set_branch.py
python ./scripts/gh_env.py
- name: Login to Container Registry
uses: docker/login-action@v1
if: ${{ steps.ev.outputs.shouldBuild == 'true' }}
with:
registry: beryju.org
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Building Docker Image
uses: docker/build-push-action@v2
with:
push: ${{ steps.ev.outputs.shouldBuild == 'true' }}
tags: |
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}
beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }}
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchNameContainer }}
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}
ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }}
file: ${{ matrix.type }}.Dockerfile
build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}

21
.github/workflows/ghcr-retention.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: ghcr-retention
on:
schedule:
- cron: '0 0 * * *' # every day at midnight
jobs:
clean-ghcr:
name: Delete old unused container images
runs-on: ubuntu-latest
steps:
- name: Delete 'dev' containers older than a week
uses: sondrelg/container-retention-policy@v1
with:
image-names: dev-server,dev-ldap,dev-proxy
cut-off: One month ago UTC
account-type: org
org-name: goauthentik
untagged-only: false
token: ${{ secrets.GHCR_CLEANUP_TOKEN }}
skip-tags: gh-next,gh-master

View File

@ -3,9 +3,6 @@ name: authentik-on-release
on:
release:
types: [published, created]
push:
branches:
- version-*
jobs:
# Build
@ -33,14 +30,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.9.7,
beryju/authentik:2021.10.1-rc1,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.9.7,
ghcr.io/goauthentik/server:2021.10.1-rc1,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.7', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.10.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +72,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.9.7,
beryju/authentik-proxy:2021.10.1-rc1,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.9.7,
ghcr.io/goauthentik/proxy:2021.10.1-rc1,
ghcr.io/goauthentik/proxy:latest
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.7', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.10.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +114,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.9.7,
beryju/authentik-ldap:2021.10.1-rc1,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.9.7,
ghcr.io/goauthentik/ldap:2021.10.1-rc1,
ghcr.io/goauthentik/ldap:latest
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.9.7', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.10.1-rc1', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -142,15 +139,13 @@ jobs:
- uses: actions/checkout@v2
- name: Run test suite in final docker images
run: |
sudo apt-get install -y pwgen
echo "PG_PASS=$(pwgen 40 1)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(pwgen 50 1)" >> .env
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
docker-compose pull -q
docker-compose up --no-start
docker-compose start postgresql redis
docker-compose run -u root server test
sentry-release:
if: ${{ github.event_name == 'release' }}
needs:
- test-release
runs-on: ubuntu-latest
@ -175,7 +170,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.9.7
version: authentik@2021.10.1-rc1
environment: beryjuorg-prod
sourcemaps: './web/dist'
url_prefix: '~/static/dist'

View File

@ -13,15 +13,14 @@ jobs:
- uses: actions/checkout@v2
- name: Pre-release test
run: |
sudo apt-get install -y pwgen
echo "AUTHENTIK_TAG=latest" >> .env
echo "PG_PASS=$(pwgen 40 1)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(pwgen 50 1)" >> .env
docker-compose pull -q
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
docker build \
--no-cache \
-t ghcr.io/goauthentik/server:latest \
-t testing:latest \
-f Dockerfile .
echo "AUTHENTIK_IMAGE=testing" >> .env
echo "AUTHENTIK_TAG=latest" >> .env
docker-compose up --no-start
docker-compose start postgresql redis
docker-compose run -u root server test

View File

@ -0,0 +1,46 @@
name: authentik-backend-translate-compile
on:
push:
branches: [ master ]
paths:
- '/locale/'
schedule:
- cron: "0 */2 * * *"
workflow_dispatch:
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
sudo apt-get update
sudo apt-get install -y gettext
scripts/ci_prepare.sh
- name: run compile
run: pipenv run ./manage.py compilemessages
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: compile-backend-translation
commit-message: "core: compile backend translations"
title: "core: compile backend translations"
delete-branch: true
signoff: true

View File

@ -27,7 +27,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
# Stage 4: Build go proxy
FROM docker.io/golang:1.17.1 AS builder
FROM docker.io/golang:1.17.2 AS builder
WORKDIR /work
@ -83,7 +83,6 @@ USER authentik
ENV TMPDIR /dev/shm/
ENV PYTHONUNBUFFERED 1
ENV prometheus_multiproc_dir /dev/shm/
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/lifecycle"
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "/lifecycle/ak", "healthcheck" ]

View File

@ -20,13 +20,24 @@ test:
lint-fix:
isort authentik tests lifecycle
black authentik tests lifecycle
codespell -I .github/codespell-words.txt -S 'web/src/locales/**' -w authentik internal cmd web/src website/src
codespell -I .github/codespell-words.txt -S 'web/src/locales/**' -w \
authentik \
internal \
cmd \
web/src \
website/src \
website/docs \
website/developer-docs
lint:
pyright authentik tests lifecycle
bandit -r authentik tests lifecycle -x node_modules
pylint authentik tests lifecycle
i18n-extract:
./manage.py makemessages --ignore web --ignore internal --ignore web --ignore web-api --ignore website -l en
cd web && npm run extract
gen-build:
./manage.py spectacular --file schema.yml
@ -69,4 +80,4 @@ migrate:
python -m lifecycle.migrate
run:
WORKERS=1 go run -v cmd/server/main.go
go run -v cmd/server/main.go

View File

@ -26,9 +26,9 @@ drf-spectacular = "*"
facebook-sdk = "*"
geoip2 = "*"
gunicorn = "*"
kubernetes = "*"
kubernetes = "==v19.15.0b1"
ldap3 = "*"
lxml = ">=4.6.3"
lxml = "*"
packaging = "*"
psycopg2-binary = "*"
pycryptodome = "*"
@ -52,10 +52,10 @@ codespell = "*"
[dev-packages]
bandit = "*"
black = "==21.5b1"
black = "==21.9b0"
bump2version = "*"
colorama = "*"
coverage = "*"
coverage = {extras = ["toml"],version = "*"}
pylint = "*"
pylint-django = "*"
pytest = "*"

1161
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,4 +1,5 @@
"""authentik administration overview"""
from django.conf import settings
from drf_spectacular.utils import extend_schema, inline_serializer
from prometheus_client import Gauge
from rest_framework.fields import IntegerField
@ -21,4 +22,7 @@ class WorkerView(APIView):
def get(self, request: Request) -> Response:
"""Get currently connected worker count."""
count = len(CELERY_APP.control.ping(timeout=0.5))
# In debug we run with `CELERY_TASK_ALWAYS_EAGER`, so tasks are ran on the main process
if settings.DEBUG:
count += 1
return Response({"count": count})

View File

@ -11,7 +11,12 @@ from structlog.stdlib import get_logger
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.events.models import Event, EventAction, Notification
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_http_session
from authentik.root.celery import CELERY_APP
@ -48,6 +53,7 @@ def clear_update_notifications():
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def update_latest_version(self: MonitoredTask):
"""Update latest version info"""
if CONFIG.y_bool("disable_update_check"):

18
authentik/api/throttle.py Normal file
View File

@ -0,0 +1,18 @@
"""Throttling classes"""
from typing import Type
from django.views import View
from rest_framework.request import Request
from rest_framework.throttling import ScopedRateThrottle
class SessionThrottle(ScopedRateThrottle):
"""Throttle based on session key"""
def allow_request(self, request: Request, view):
if request._request.user.is_superuser:
return True
return super().allow_request(request, view)
def get_cache_key(self, request: Request, view: Type[View]) -> str:
return f"authentik-throttle-session-{request._request.session.session_key}"

View File

@ -30,7 +30,8 @@ from authentik.events.api.notification_transport import NotificationTransportVie
from authentik.flows.api.bindings import FlowStageBindingViewSet
from authentik.flows.api.flows import FlowViewSet
from authentik.flows.api.stages import StageViewSet
from authentik.flows.views import FlowExecutorView
from authentik.flows.views.executor import FlowExecutorView
from authentik.flows.views.inspector import FlowInspectorView
from authentik.outposts.api.outposts import OutpostViewSet
from authentik.outposts.api.service_connections import (
DockerServiceConnectionViewSet,
@ -67,6 +68,11 @@ from authentik.stages.authenticator_duo.api import (
DuoAdminDeviceViewSet,
DuoDeviceViewSet,
)
from authentik.stages.authenticator_sms.api import (
AuthenticatorSMSStageViewSet,
SMSAdminDeviceViewSet,
SMSDeviceViewSet,
)
from authentik.stages.authenticator_static.api import (
AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
@ -164,6 +170,7 @@ router.register("propertymappings/scope", ScopeMappingViewSet)
router.register("propertymappings/notification", NotificationWebhookMappingViewSet)
router.register("authenticators/duo", DuoDeviceViewSet)
router.register("authenticators/sms", SMSDeviceViewSet)
router.register("authenticators/static", StaticDeviceViewSet)
router.register("authenticators/totp", TOTPDeviceViewSet)
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
@ -172,6 +179,11 @@ router.register(
DuoAdminDeviceViewSet,
basename="admin-duodevice",
)
router.register(
"authenticators/admin/sms",
SMSAdminDeviceViewSet,
basename="admin-smsdevice",
)
router.register(
"authenticators/admin/static",
StaticAdminDeviceViewSet,
@ -186,6 +198,7 @@ router.register(
router.register("stages/all", StageViewSet)
router.register("stages/authenticator/duo", AuthenticatorDuoStageViewSet)
router.register("stages/authenticator/sms", AuthenticatorSMSStageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
router.register("stages/authenticator/validate", AuthenticatorValidateStageViewSet)
@ -228,6 +241,11 @@ urlpatterns = (
FlowExecutorView.as_view(),
name="flow-executor",
),
path(
"flows/inspector/<slug:flow_slug>/",
FlowInspectorView.as_view(),
name="flow-inspector",
),
path("sentry/", SentryTunnelView.as_view(), name="sentry"),
path("schema/", cache_page(86400)(SpectacularAPIView.as_view()), name="schema"),
]

View File

@ -22,7 +22,7 @@ from drf_spectacular.utils import (
)
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
from rest_framework.fields import CharField, DictField, JSONField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
@ -103,6 +103,7 @@ class UserSelfSerializer(ModelSerializer):
avatar = CharField(read_only=True)
groups = SerializerMethodField()
uid = CharField(read_only=True)
settings = DictField(source="attributes.settings", default=dict)
@extend_schema_field(
ListSerializer(
@ -133,6 +134,7 @@ class UserSelfSerializer(ModelSerializer):
"email",
"avatar",
"uid",
"settings",
]
extra_kwargs = {
"is_active": {"read_only": True},

View File

@ -8,7 +8,7 @@ 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.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS

View File

@ -0,0 +1,221 @@
# Generated by Django 3.2.8 on 2021-10-10 16:16
from os import environ
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import User
db_alias = schema_editor.connection.alias
akadmin, _ = User.objects.using(db_alias).get_or_create(
username="akadmin", email="root@localhost", name="authentik Default Admin"
)
if "TF_BUILD" in environ or "AK_ADMIN_PASS" in environ or settings.TEST:
akadmin.set_password(environ.get("AK_ADMIN_PASS", "akadmin"), signal=False) # noqa # nosec
else:
akadmin.set_unusable_password()
akadmin.save()
def create_default_admin_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
User = apps.get_model("authentik_core", "User")
# Creates a default admin group
group, _ = Group.objects.using(db_alias).get_or_create(
is_superuser=True,
defaults={
"name": "authentik Admins",
},
)
group.users.set(User.objects.filter(username="akadmin"))
group.save()
class Migration(migrations.Migration):
replaces = [
("authentik_core", "0002_auto_20200523_1133"),
("authentik_core", "0003_default_user"),
("authentik_core", "0004_auto_20200703_2213"),
("authentik_core", "0005_token_intent"),
("authentik_core", "0006_auto_20200709_1608"),
("authentik_core", "0007_auto_20200815_1841"),
("authentik_core", "0008_auto_20200824_1532"),
("authentik_core", "0009_group_is_superuser"),
("authentik_core", "0010_auto_20200917_1021"),
("authentik_core", "0011_provider_name_temp"),
]
dependencies = [
("authentik_core", "0001_initial"),
("authentik_flows", "0003_auto_20200523_1133"),
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.RemoveField(
model_name="application",
name="skip_authorization",
),
migrations.AddField(
model_name="source",
name="authentication_flow",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Flow to use when authenticating existing users.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="source_authentication",
to="authentik_flows.flow",
),
),
migrations.AddField(
model_name="source",
name="enrollment_flow",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Flow to use when enrolling new users.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="source_enrollment",
to="authentik_flows.flow",
),
),
migrations.AddField(
model_name="provider",
name="authorization_flow",
field=models.ForeignKey(
help_text="Flow used when authorizing this provider.",
on_delete=django.db.models.deletion.CASCADE,
related_name="provider_authorization",
to="authentik_flows.flow",
),
),
migrations.RemoveField(
model_name="user",
name="is_superuser",
),
migrations.RemoveField(
model_name="user",
name="is_staff",
),
migrations.RunPython(
code=create_default_user,
),
migrations.AddField(
model_name="user",
name="is_superuser",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="user",
name="is_staff",
field=models.BooleanField(default=False),
),
migrations.AlterModelOptions(
name="application",
options={"verbose_name": "Application", "verbose_name_plural": "Applications"},
),
migrations.AlterModelOptions(
name="user",
options={
"permissions": (("reset_user_password", "Reset Password"),),
"verbose_name": "User",
"verbose_name_plural": "Users",
},
),
migrations.AddField(
model_name="token",
name="intent",
field=models.TextField(
choices=[("verification", "Intent Verification"), ("api", "Intent Api")],
default="verification",
),
),
migrations.AlterField(
model_name="source",
name="slug",
field=models.SlugField(help_text="Internal source name, used in URLs.", unique=True),
),
migrations.AlterField(
model_name="user",
name="first_name",
field=models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
migrations.RemoveField(
model_name="user",
name="groups",
),
migrations.AddField(
model_name="user",
name="groups",
field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
),
migrations.RemoveField(
model_name="user",
name="is_superuser",
),
migrations.RemoveField(
model_name="user",
name="is_staff",
),
migrations.AddField(
model_name="user",
name="pb_groups",
field=models.ManyToManyField(related_name="users", to="authentik_core.Group"),
),
migrations.AddField(
model_name="group",
name="is_superuser",
field=models.BooleanField(
default=False, help_text="Users added to this group will be superusers."
),
),
migrations.RunPython(
code=create_default_admin_group,
),
migrations.AlterModelManagers(
name="user",
managers=[
("objects", authentik.core.models.UserManager()),
],
),
migrations.AlterModelOptions(
name="user",
options={
"permissions": (
("reset_user_password", "Reset Password"),
("impersonate", "Can impersonate other users"),
),
"verbose_name": "User",
"verbose_name_plural": "Users",
},
),
migrations.AddField(
model_name="provider",
name="name_temp",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -0,0 +1,118 @@
# Generated by Django 3.2.8 on 2021-10-12 15:36
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Token = apps.get_model("authentik_core", "Token")
for token in Token.objects.using(db_alias).all():
token.key = token.pk.hex
token.save()
class Migration(migrations.Migration):
replaces = [
("authentik_core", "0012_auto_20201003_1737"),
("authentik_core", "0013_auto_20201003_2132"),
("authentik_core", "0014_auto_20201018_1158"),
("authentik_core", "0015_application_icon"),
("authentik_core", "0016_auto_20201202_2234"),
]
dependencies = [
("authentik_providers_saml", "0006_remove_samlprovider_name"),
("authentik_providers_oauth2", "0006_remove_oauth2provider_name"),
("authentik_core", "0011_provider_name_temp"),
]
operations = [
migrations.RenameField(
model_name="provider",
old_name="name_temp",
new_name="name",
),
migrations.AddField(
model_name="token",
name="identifier",
field=models.TextField(default=""),
preserve_default=False,
),
migrations.AlterField(
model_name="token",
name="intent",
field=models.TextField(
choices=[
("verification", "Intent Verification"),
("api", "Intent Api"),
("recovery", "Intent Recovery"),
],
default="verification",
),
),
migrations.AlterUniqueTogether(
name="token",
unique_together={("identifier", "user")},
),
migrations.AddField(
model_name="token",
name="key",
field=models.TextField(default=authentik.core.models.default_token_key),
),
migrations.AlterUniqueTogether(
name="token",
unique_together=set(),
),
migrations.AlterField(
model_name="token",
name="identifier",
field=models.SlugField(max_length=255),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["key"], name="authentik_co_key_e45007_idx"),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["identifier"], name="authentik_co_identif_1a34a8_idx"),
),
migrations.RunPython(
code=set_default_token_key,
),
migrations.RemoveField(
model_name="application",
name="meta_icon_url",
),
migrations.AddField(
model_name="application",
name="meta_icon",
field=models.FileField(blank=True, default="", upload_to="application-icons/"),
),
migrations.RemoveIndex(
model_name="token",
name="authentik_co_key_e45007_idx",
),
migrations.RemoveIndex(
model_name="token",
name="authentik_co_identif_1a34a8_idx",
),
migrations.RenameField(
model_name="user",
old_name="pb_groups",
new_name="ak_groups",
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["identifier"], name="authentik_c_identif_d9d032_idx"),
),
migrations.AddIndex(
model_name="token",
index=models.Index(fields=["key"], name="authentik_c_key_f71355_idx"),
),
]

View File

@ -0,0 +1,210 @@
# Generated by Django 3.2.8 on 2021-10-10 16:12
import uuid
from os import environ
import django.core.validators
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.models import Count
import authentik.core.models
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache
session_keys = cache.keys(KEY_PREFIX + "*")
cache.delete_many(session_keys)
def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Token = apps.get_model("authentik_core", "token")
identifiers = (
Token.objects.using(db_alias)
.values("identifier")
.annotate(identifier_count=Count("identifier"))
.filter(identifier_count__gt=1)
)
for ident in identifiers:
Token.objects.using(db_alias).filter(identifier=ident["identifier"]).delete()
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
# We have to use a direct import here, otherwise we get an object manager error
from authentik.core.models import Token, TokenIntents, User
db_alias = schema_editor.connection.alias
akadmin = User.objects.using(db_alias).filter(username="akadmin")
if not akadmin.exists():
return
if "AK_ADMIN_TOKEN" not in environ:
return
Token.objects.using(db_alias).create(
identifier="authentik-boostrap-token",
user=akadmin.first(),
intent=TokenIntents.INTENT_API,
expiring=False,
key=environ["AK_ADMIN_TOKEN"],
)
class Migration(migrations.Migration):
replaces = [
("authentik_core", "0018_auto_20210330_1345"),
("authentik_core", "0019_source_managed"),
("authentik_core", "0020_source_user_matching_mode"),
("authentik_core", "0021_alter_application_slug"),
("authentik_core", "0022_authenticatedsession"),
("authentik_core", "0023_alter_application_meta_launch_url"),
("authentik_core", "0024_alter_token_identifier"),
("authentik_core", "0025_alter_application_meta_icon"),
("authentik_core", "0026_alter_application_meta_icon"),
("authentik_core", "0027_bootstrap_token"),
("authentik_core", "0028_alter_token_intent"),
]
dependencies = [
("authentik_core", "0017_managed"),
]
operations = [
migrations.AlterModelOptions(
name="token",
options={
"permissions": (("view_token_key", "View token's key"),),
"verbose_name": "Token",
"verbose_name_plural": "Tokens",
},
),
migrations.AddField(
model_name="source",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
migrations.AddField(
model_name="source",
name="user_matching_mode",
field=models.TextField(
choices=[
("identifier", "Use the source-specific identifier"),
(
"email_link",
"Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses.",
),
(
"email_deny",
"Use the user's email address, but deny enrollment when the email address already exists.",
),
(
"username_link",
"Link to a user with identical username. Can have security implications when a username is used with another source.",
),
(
"username_deny",
"Use the user's username, but deny enrollment when the username already exists.",
),
],
default="identifier",
help_text="How the source determines if an existing user should be authenticated or a new user enrolled.",
),
),
migrations.AlterField(
model_name="application",
name="slug",
field=models.SlugField(
help_text="Internal application name, used in URLs.", unique=True
),
),
migrations.CreateModel(
name="AuthenticatedSession",
fields=[
(
"expires",
models.DateTimeField(default=authentik.core.models.default_token_duration),
),
("expiring", models.BooleanField(default=True)),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("session_key", models.CharField(max_length=40)),
("last_ip", models.TextField()),
("last_user_agent", models.TextField(blank=True)),
("last_used", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"abstract": False,
},
),
migrations.RunPython(
code=migrate_sessions,
),
migrations.AlterField(
model_name="application",
name="meta_launch_url",
field=models.TextField(
blank=True, default="", validators=[django.core.validators.URLValidator()]
),
),
migrations.RunPython(
code=fix_duplicates,
),
migrations.AlterField(
model_name="token",
name="identifier",
field=models.SlugField(max_length=255, unique=True),
),
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(default=None, null=True, upload_to="application-icons/"),
),
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(
default=None, max_length=500, null=True, upload_to="application-icons/"
),
),
migrations.AlterModelOptions(
name="authenticatedsession",
options={
"verbose_name": "Authenticated Session",
"verbose_name_plural": "Authenticated Sessions",
},
),
migrations.RunPython(
code=create_default_user_token,
),
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

@ -22,7 +22,7 @@ from authentik.flows.planner import (
PLAN_CONTEXT_SSO,
FlowPlanner,
)
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys
from authentik.stages.password import BACKEND_INBUILT

View File

@ -15,7 +15,12 @@ from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
from structlog.stdlib import get_logger
from authentik.core.models import AuthenticatedSession, ExpiringModel
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.lib.config import CONFIG
from authentik.root.celery import CELERY_APP
@ -23,6 +28,7 @@ LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def clean_expired_models(self: MonitoredTask):
"""Remove expired objects"""
messages = []
@ -50,10 +56,11 @@ def clean_expired_models(self: MonitoredTask):
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def backup_database(self: MonitoredTask): # pragma: no cover
"""Database backup"""
self.result_timeout_hours = 25
if SERVICE_HOST_ENV_NAME in environ and not CONFIG.y("postgresql.s3_backup"):
if SERVICE_HOST_ENV_NAME in environ and not CONFIG.y("postgresql.s3_backup.bucket"):
LOGGER.info("Running in k8s and s3 backups are not configured, skipping")
self.set_status(
TaskResult(

View File

@ -5,7 +5,7 @@
{% block head_before %}
{{ block.super }}
{% if flow.compatibility_mode %}
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% endblock %}

View File

@ -4,7 +4,7 @@ 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.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.tests.utils import get_request

View File

@ -14,4 +14,5 @@ class FlowInterfaceView(TemplateView):
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
kwargs["inspector"] = "inspector" in self.request.GET
return super().get_context_data(**kwargs)

View File

@ -99,6 +99,7 @@ class CertificateKeyPairSerializer(ModelSerializer):
"private_key_available",
"certificate_download_url",
"private_key_download_url",
"managed",
]
extra_kwargs = {
"key_data": {"write_only": True},
@ -134,7 +135,7 @@ class CertificateKeyPairFilter(FilterSet):
class Meta:
model = CertificateKeyPair
fields = ["name"]
fields = ["name", "managed"]
class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):

View File

@ -1,4 +1,6 @@
"""authentik crypto app config"""
from importlib import import_module
from django.apps import AppConfig
@ -8,3 +10,6 @@ class AuthentikCryptoConfig(AppConfig):
name = "authentik.crypto"
label = "authentik_crypto"
verbose_name = "authentik Crypto"
def ready(self):
import_module("authentik.crypto.managed")

View File

@ -24,16 +24,17 @@ class CertificateBuilder:
self.__builder = None
self.__certificate = None
self.common_name = "authentik Self-signed Certificate"
self.cert = CertificateKeyPair()
def save(self) -> Optional[CertificateKeyPair]:
"""Save generated certificate as model"""
if not self.__certificate:
raise ValueError("Certificated hasn't been built yet")
return CertificateKeyPair.objects.create(
name=self.common_name,
certificate_data=self.certificate,
key_data=self.private_key,
)
self.cert.name = self.common_name
self.cert.certificate_data = self.certificate
self.cert.key_data = self.private_key
self.cert.save()
return self.cert
def build(
self,

View File

@ -0,0 +1,40 @@
"""Crypto managed objects"""
from datetime import datetime
from typing import Optional
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
from authentik.managed.manager import ObjectManager
MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"
class CryptoManager(ObjectManager):
"""Crypto managed objects"""
def _create(self, cert: Optional[CertificateKeyPair] = None):
builder = CertificateBuilder()
builder.common_name = "goauthentik.io"
builder.build(
subject_alt_names=["goauthentik.io"],
validity_days=360,
)
if not cert:
cert = CertificateKeyPair()
cert.certificate_data = builder.certificate
cert.key_data = builder.private_key
cert.name = "authentik Internal JWT Certificate"
cert.managed = MANAGED_KEY
cert.save()
def reconcile(self):
certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY)
if not certs.exists():
self._create()
return []
cert: CertificateKeyPair = certs.first()
now = datetime.now()
if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after:
self._create(cert)
return []
return []

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.8 on 2021-10-09 17:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0002_create_self_signed_kp"),
]
operations = [
migrations.AddField(
model_name="certificatekeypair",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
]

View File

@ -13,9 +13,10 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from authentik.lib.models import CreatedUpdatedModel
from authentik.managed.models import ManagedModel
class CertificateKeyPair(CreatedUpdatedModel):
class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
is set, otherwise it can be used to verify remote data."""

View File

@ -0,0 +1,831 @@
# Generated by Django 3.2.8 on 2021-10-10 16:01
import uuid
from datetime import timedelta
from typing import Iterable
import django.core.validators
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.events.models
from authentik.events.models import EventAction, NotificationSeverity, TransportMode
def convert_user_to_json(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Event = apps.get_model("authentik_events", "Event")
db_alias = schema_editor.connection.alias
for event in Event.objects.all():
event.delete()
# Because event objects cannot be updated, we have to re-create them
event.pk = None
event.user_json = authentik.events.models.get_user(event.user) if event.user else {}
event._state.adding = True
event.save()
def notify_configuration_error(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
)
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-configuration-error",
defaults={"action": EventAction.CONFIGURATION_ERROR},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-configuration-error",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy,
defaults={
"order": 0,
},
)
def notify_update(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
)
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-update",
defaults={"action": EventAction.UPDATE_AVAILABLE},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-update",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy,
defaults={
"order": 0,
},
)
def notify_exception(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
admin_group = (
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
)
policy_policy_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-policy-exception",
defaults={"action": EventAction.POLICY_EXCEPTION},
)
policy_pm_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
name="default-match-property-mapping-exception",
defaults={"action": EventAction.PROPERTY_MAPPING_EXCEPTION},
)
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
name="default-notify-exception",
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
)
trigger.transports.set(
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
)
trigger.save()
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy_policy_exc,
defaults={
"order": 0,
},
)
PolicyBinding.objects.using(db_alias).update_or_create(
target=trigger,
policy=policy_pm_exc,
defaults={
"order": 1,
},
)
def transport_email_global(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
NotificationTransport.objects.using(db_alias).update_or_create(
name="default-email-transport",
defaults={"mode": TransportMode.EMAIL},
)
def token_view_to_secret_view(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.events.models import EventAction
db_alias = schema_editor.connection.alias
Event = apps.get_model("authentik_events", "Event")
events = Event.objects.using(db_alias).filter(action="token_view")
for event in events:
event.context["secret"] = event.context.pop("token")
event.action = EventAction.SECRET_VIEW
Event.objects.using(db_alias).bulk_update(events, ["context", "action"])
# Taken from https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
def progress_bar(
iterable: Iterable,
prefix="Writing: ",
suffix=" finished",
decimals=1,
length=100,
fill="",
print_end="\r",
):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
print_end - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
total = len(iterable)
if total < 1:
return
def print_progress_bar(iteration):
"""Progress Bar Printing Function"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + "-" * (length - filledLength)
print(f"\r{prefix} |{bar}| {percent}% {suffix}", end=print_end)
# Initial Call
print_progress_bar(0)
# Update Progress Bar
for i, item in enumerate(iterable):
yield item
print_progress_bar(i + 1)
# Print New Line on Complete
print()
def update_expires(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Event = apps.get_model("authentik_events", "event")
all_events = Event.objects.using(db_alias).all()
if all_events.count() < 1:
return
print("\nAdding expiry to events, this might take a couple of minutes...")
for event in progress_bar(all_events):
event.expires = event.created + timedelta(days=365)
event.save()
class Migration(migrations.Migration):
replaces = [
("authentik_events", "0001_initial"),
("authentik_events", "0002_auto_20200918_2116"),
("authentik_events", "0003_auto_20200917_1155"),
("authentik_events", "0004_auto_20200921_1829"),
("authentik_events", "0005_auto_20201005_2139"),
("authentik_events", "0006_auto_20201017_2024"),
("authentik_events", "0007_auto_20201215_0939"),
("authentik_events", "0008_auto_20201220_1651"),
("authentik_events", "0009_auto_20201227_1210"),
("authentik_events", "0010_notification_notificationtransport_notificationrule"),
("authentik_events", "0011_notification_rules_default_v1"),
("authentik_events", "0012_auto_20210202_1821"),
("authentik_events", "0013_auto_20210209_1657"),
("authentik_events", "0014_expiry"),
("authentik_events", "0015_alter_event_action"),
("authentik_events", "0016_add_tenant"),
("authentik_events", "0017_alter_event_action"),
("authentik_events", "0018_auto_20210911_2217"),
("authentik_events", "0019_alter_notificationtransport_webhook_url"),
]
initial = True
dependencies = [
("authentik_policies", "0004_policy_execution_logging"),
("authentik_core", "0016_auto_20201202_2234"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
("authentik_core", "0028_alter_token_intent"),
]
operations = [
migrations.CreateModel(
name="Event",
fields=[
(
"event_uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
(
"action",
models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("IMPERSONATION_STARTED", "impersonation_started"),
("IMPERSONATION_ENDED", "impersonation_ended"),
("CUSTOM", "custom"),
]
),
),
("date", models.DateTimeField(auto_now_add=True)),
("app", models.TextField()),
("context", models.JSONField(blank=True, default=dict)),
("client_ip", models.GenericIPAddressField(null=True)),
("created", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
("user_json", models.JSONField(default=dict)),
],
options={
"verbose_name": "Event",
"verbose_name_plural": "Events",
},
),
migrations.RunPython(
code=convert_user_to_json,
),
migrations.RemoveField(
model_name="event",
name="user",
),
migrations.RenameField(
model_name="event",
old_name="user_json",
new_name="user",
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("sign_up", "Sign Up"),
("authorize_application", "Authorize Application"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.RemoveField(
model_name="event",
name="date",
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.CreateModel(
name="NotificationTransport",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField(unique=True)),
(
"mode",
models.TextField(
choices=[
("webhook", "Generic Webhook"),
("webhook_slack", "Slack Webhook (Slack/Discord)"),
("email", "Email"),
]
),
),
("webhook_url", models.TextField(blank=True)),
],
options={
"verbose_name": "Notification Transport",
"verbose_name_plural": "Notification Transports",
},
),
migrations.CreateModel(
name="NotificationRule",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policybindingmodel",
),
),
("name", models.TextField(unique=True)),
(
"severity",
models.TextField(
choices=[("notice", "Notice"), ("warning", "Warning"), ("alert", "Alert")],
default="notice",
help_text="Controls which severity level the created notifications will have.",
),
),
(
"group",
models.ForeignKey(
blank=True,
help_text="Define which group of users this notification should be sent and shown to. If left empty, Notification won't ben sent.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_core.group",
),
),
(
"transports",
models.ManyToManyField(
help_text="Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.",
to="authentik_events.NotificationTransport",
),
),
],
options={
"verbose_name": "Notification Rule",
"verbose_name_plural": "Notification Rules",
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.CreateModel(
name="Notification",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
(
"severity",
models.TextField(
choices=[("notice", "Notice"), ("warning", "Warning"), ("alert", "Alert")]
),
),
("body", models.TextField()),
("created", models.DateTimeField(auto_now_add=True)),
("seen", models.BooleanField(default=False)),
(
"event",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_events.event",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "Notification",
"verbose_name_plural": "Notifications",
},
),
migrations.RunPython(
code=transport_email_global,
),
migrations.RunPython(
code=notify_configuration_error,
),
migrations.RunPython(
code=notify_update,
),
migrations.RunPython(
code=notify_exception,
),
migrations.AddField(
model_name="notificationtransport",
name="send_once",
field=models.BooleanField(
default=False,
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.RunPython(
code=token_view_to_secret_view,
),
migrations.AddField(
model_name="event",
name="expires",
field=models.DateTimeField(default=authentik.events.models.default_event_duration),
),
migrations.AddField(
model_name="event",
name="expiring",
field=models.BooleanField(default=True),
),
migrations.RunPython(
code=update_expires,
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AddField(
model_name="event",
name="tenant",
field=models.JSONField(blank=True, default=authentik.events.models.default_tenant),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.CreateModel(
name="NotificationWebhookMapping",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.propertymapping",
),
),
],
options={
"verbose_name": "Notification Webhook Mapping",
"verbose_name_plural": "Notification Webhook Mappings",
},
bases=("authentik_core.propertymapping",),
),
migrations.AddField(
model_name="notificationtransport",
name="webhook_mapping",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_events.notificationwebhookmapping",
),
),
migrations.AlterField(
model_name="notificationtransport",
name="webhook_url",
field=models.TextField(blank=True, validators=[django.core.validators.URLValidator()]),
),
]

View File

@ -7,7 +7,9 @@ from typing import Any, Optional
from celery import Task
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
from prometheus_client import Gauge
from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.utils.errors import exception_to_string
@ -18,6 +20,8 @@ GAUGE_TASKS = Gauge(
["task_name", "task_uid", "status"],
)
LOGGER = get_logger()
class TaskResultStatus(Enum):
"""Possible states of tasks"""
@ -25,6 +29,7 @@ class TaskResultStatus(Enum):
SUCCESSFUL = 1
WARNING = 2
ERROR = 4
UNKNOWN = 8
@dataclass
@ -76,7 +81,7 @@ class TaskInfo:
@staticmethod
def by_name(name: str) -> Optional["TaskInfo"]:
"""Get TaskInfo Object by name"""
return cache.get(f"task_{name}")
return cache.get(f"task_{name}", None)
def delete(self):
"""Delete task info from cache"""
@ -107,6 +112,30 @@ class TaskInfo:
cache.set(key, self, timeout=timeout_hours * 60 * 60)
def prefill_task():
"""Ensure a task's details are always in cache, so it can always be triggered via API"""
def inner_wrap(func):
status = TaskInfo.by_name(func.__name__)
if status:
return func
TaskInfo(
task_name=func.__name__,
task_description=func.__doc__,
result=TaskResult(TaskResultStatus.UNKNOWN, messages=[_("Task has not been run yet.")]),
task_call_module=func.__module__,
task_call_func=func.__name__,
# We don't have real values for these attributes but they cannot be null
start_timestamp=default_timer(),
finish_timestamp=default_timer(),
finish_time=datetime.now(),
).save(86400)
LOGGER.debug("prefilled task", task_name=func.__name__)
return func
return inner_wrap
class MonitoredTask(Task):
"""Task which can save its state to the cache"""

View File

@ -12,7 +12,7 @@ from authentik.core.signals import password_changed
from authentik.events.models import Event, EventAction
from authentik.events.tasks import event_notification_handler
from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.flows.views.executor 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

View File

@ -32,7 +32,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cach
from authentik.flows.transfer.common import DataclassEncoder
from authentik.flows.transfer.exporter import FlowExporter
from authentik.flows.transfer.importer import FlowImporter
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
from authentik.lib.views import bad_request_message
LOGGER = get_logger()
@ -334,6 +334,9 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
# pylint: disable=unused-argument
def execute(self, request: Request, slug: str):
"""Execute flow for current user"""
# Because we pre-plan the flow here, and not in the planner, we need to manually clear
# the history of the inspector
request.session[SESSION_KEY_HISTORY] = []
flow: Flow = self.get_object()
planner = FlowPlanner(flow)
planner.use_cache = False

View File

@ -0,0 +1,180 @@
# Generated by Django 3.2.8 on 2021-10-10 16:08
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [
("authentik_flows", "0001_initial"),
("authentik_flows", "0003_auto_20200523_1133"),
("authentik_flows", "0006_auto_20200629_0857"),
("authentik_flows", "0007_auto_20200703_2059"),
]
initial = True
dependencies = [
("authentik_policies", "0001_initial"),
("authentik_policies", "0002_auto_20200528_1647"),
]
operations = [
migrations.CreateModel(
name="Flow",
fields=[
(
"flow_uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("slug", models.SlugField(unique=True)),
(
"designation",
models.CharField(
choices=[
("authentication", "Authentication"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("password_change", "Password Change"),
],
max_length=100,
),
),
(
"pbm",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="+",
to="authentik_policies.policybindingmodel",
),
),
],
options={
"verbose_name": "Flow",
"verbose_name_plural": "Flows",
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.CreateModel(
name="Stage",
fields=[
(
"stage_uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
],
),
migrations.CreateModel(
name="FlowStageBinding",
fields=[
(
"policybindingmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
to="authentik_policies.policybindingmodel",
),
),
(
"fsb_uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
(
"re_evaluate_policies",
models.BooleanField(
default=False,
help_text="When this option is enabled, the planner will re-evaluate policies bound to this.",
),
),
("order", models.IntegerField()),
(
"target",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.flow"
),
),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.stage"
),
),
],
options={
"verbose_name": "Flow Stage Binding",
"verbose_name_plural": "Flow Stage Bindings",
"ordering": ["order", "target"],
"unique_together": {("target", "stage", "order")},
},
bases=("authentik_policies.policybindingmodel",),
),
migrations.AddField(
model_name="flow",
name="stages",
field=models.ManyToManyField(
blank=True, through="authentik_flows.FlowStageBinding", to="authentik_flows.Stage"
),
),
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("password_change", "Password Change"),
],
max_length=100,
),
),
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_setup", "Stage Setup"),
],
max_length=100,
),
),
migrations.RenameField(
model_name="flow",
old_name="pbm",
new_name="policybindingmodel_ptr",
),
migrations.AlterField(
model_name="flow",
name="policybindingmodel_ptr",
field=models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
to="authentik_policies.policybindingmodel",
),
),
]

View File

@ -0,0 +1,171 @@
# Generated by Django 3.2.8 on 2021-10-10 16:08
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.lib.models
from authentik.flows.models import FlowDesignation
def update_flow_designation(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Flow = apps.get_model("authentik_flows", "Flow")
db_alias = schema_editor.connection.alias
for flow in Flow.objects.using(db_alias).all():
if flow.designation == "stage_setup":
flow.designation = FlowDesignation.STAGE_CONFIGURATION
flow.save()
# First stage for default-source-enrollment flow (prompt stage)
# needs to have its policy re-evaluated
def update_default_source_enrollment_flow_binding(
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
):
Flow = apps.get_model("authentik_flows", "Flow")
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
db_alias = schema_editor.connection.alias
flows = Flow.objects.using(db_alias).filter(slug="default-source-enrollment")
if not flows.exists():
return
flow = flows.first()
binding = FlowStageBinding.objects.get(target=flow, order=0)
binding.re_evaluate_policies = True
binding.save()
class Migration(migrations.Migration):
replaces = [
("authentik_flows", "0012_auto_20200908_1542"),
("authentik_flows", "0013_auto_20200924_1605"),
("authentik_flows", "0014_auto_20200925_2332"),
("authentik_flows", "0015_flowstagebinding_evaluate_on_plan"),
("authentik_flows", "0016_auto_20201202_1307"),
("authentik_flows", "0017_auto_20210329_1334"),
]
dependencies = [
("authentik_flows", "0011_flow_title"),
]
operations = [
migrations.AlterField(
model_name="flowstagebinding",
name="stage",
field=authentik.lib.models.InheritanceForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_flows.stage"
),
),
migrations.AlterField(
model_name="stage",
name="name",
field=models.TextField(unique=True),
),
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_configuration", "Stage Configuration"),
],
max_length=100,
),
),
migrations.RunPython(
code=update_flow_designation,
),
migrations.AlterModelOptions(
name="flowstagebinding",
options={
"ordering": ["target", "order"],
"verbose_name": "Flow Stage Binding",
"verbose_name_plural": "Flow Stage Bindings",
},
),
migrations.AlterField(
model_name="flowstagebinding",
name="re_evaluate_policies",
field=models.BooleanField(
default=False,
help_text="When this option is enabled, the planner will re-evaluate policies bound to this binding.",
),
),
migrations.RunPython(
code=update_default_source_enrollment_flow_binding,
),
migrations.AlterField(
model_name="flowstagebinding",
name="re_evaluate_policies",
field=models.BooleanField(
default=False, help_text="Evaluate policies when the Stage is present to the user."
),
),
migrations.AddField(
model_name="flowstagebinding",
name="evaluate_on_plan",
field=models.BooleanField(
default=True,
help_text="Evaluate policies during the Flow planning process. Disable this for input-based policies.",
),
),
migrations.AddField(
model_name="flow",
name="background",
field=models.FileField(
blank=True,
default="../static/dist/assets/images/flow_background.jpg",
help_text="Background shown during execution",
upload_to="flow-backgrounds/",
),
),
migrations.AlterField(
model_name="flow",
name="designation",
field=models.CharField(
choices=[
("authentication", "Authentication"),
("authorization", "Authorization"),
("invalidation", "Invalidation"),
("enrollment", "Enrollment"),
("unenrollment", "Unrenollment"),
("recovery", "Recovery"),
("stage_configuration", "Stage Configuration"),
],
help_text="Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.",
max_length=100,
),
),
migrations.AlterField(
model_name="flow",
name="slug",
field=models.SlugField(help_text="Visible in the URL.", unique=True),
),
migrations.AlterField(
model_name="flow",
name="title",
field=models.TextField(help_text="Shown as the Title in Flow pages."),
),
migrations.AlterModelOptions(
name="flow",
options={
"permissions": [
("export_flow", "Can export a Flow"),
("view_flow_cache", "View Flow's cache metrics"),
("clear_flow_cache", "Clear Flow's cache metrics"),
],
"verbose_name": "Flow",
"verbose_name_plural": "Flows",
},
),
]

View File

@ -0,0 +1,64 @@
# Generated by Django 3.2.8 on 2021-10-10 16:10
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [
("authentik_flows", "0019_alter_flow_background"),
("authentik_flows", "0020_flow_compatibility_mode"),
("authentik_flows", "0021_flowstagebinding_invalid_response_action"),
("authentik_flows", "0022_alter_flowstagebinding_invalid_response_action"),
("authentik_flows", "0023_alter_flow_background"),
("authentik_flows", "0024_alter_flow_compatibility_mode"),
]
dependencies = [
("authentik_flows", "0018_oob_flows"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="background",
field=models.FileField(
default=None,
help_text="Background shown during execution",
null=True,
upload_to="flow-backgrounds/",
),
),
migrations.AddField(
model_name="flowstagebinding",
name="invalid_response_action",
field=models.TextField(
choices=[
("retry", "Retry"),
("restart", "Restart"),
("restart_with_context", "Restart With Context"),
],
default="retry",
help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT restarts the flow while keeping the current context.",
),
),
migrations.AlterField(
model_name="flow",
name="background",
field=models.FileField(
default=None,
help_text="Background shown during execution",
max_length=500,
null=True,
upload_to="flow-backgrounds/",
),
),
migrations.AddField(
model_name="flow",
name="compatibility_mode",
field=models.BooleanField(
default=False,
help_text="Enable compatibility mode, increases compatibility with password managers on mobile devices.",
),
),
]

View File

@ -18,7 +18,7 @@ from authentik.flows.challenge import (
)
from authentik.flows.models import InvalidResponseAction
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView
from authentik.flows.views.executor import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
LOGGER = get_logger()

View File

@ -14,7 +14,7 @@ from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.flows.planner import FlowPlan, FlowPlanner
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG
from authentik.policies.dummy.models import DummyPolicy
from authentik.policies.models import PolicyBinding
@ -38,13 +38,13 @@ TO_STAGE_RESPONSE_MOCK = MagicMock(side_effect=to_stage_response)
class TestFlowExecutor(APITestCase):
"""Test views logic"""
"""Test executor"""
def setUp(self):
self.request_factory = RequestFactory()
@patch(
"authentik.flows.views.to_stage_response",
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_existing_plan_diff_flow(self):
@ -62,7 +62,7 @@ class TestFlowExecutor(APITestCase):
session.save()
cancel_mock = MagicMock()
with patch("authentik.flows.views.FlowExecutorView.cancel", cancel_mock):
with patch("authentik.flows.views.executor.FlowExecutorView.cancel", cancel_mock):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
@ -70,7 +70,7 @@ class TestFlowExecutor(APITestCase):
self.assertEqual(cancel_mock.call_count, 2)
@patch(
"authentik.flows.views.to_stage_response",
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
@patch(
@ -105,7 +105,7 @@ class TestFlowExecutor(APITestCase):
)
@patch(
"authentik.flows.views.to_stage_response",
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_empty_flow(self):
@ -124,7 +124,7 @@ class TestFlowExecutor(APITestCase):
self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
@patch(
"authentik.flows.views.to_stage_response",
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_flow_redirect(self):
@ -175,7 +175,7 @@ class TestFlowExecutor(APITestCase):
self.assertEqual(len(plan.bindings), 1)
@patch(
"authentik.flows.views.to_stage_response",
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_reevaluate_remove_last(self):

View File

@ -0,0 +1,92 @@
"""Flow inspector tests"""
from json import loads
from django.test.client import RequestFactory
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.stages.dummy.models import DummyStage
from authentik.stages.identification.models import IdentificationStage, UserFields
class TestFlowInspector(APITestCase):
"""Test inspector"""
def setUp(self):
self.request_factory = RequestFactory()
self.admin = User.objects.get(username="akadmin")
self.client.force_login(self.admin)
def test(self):
"""test inspector"""
flow = Flow.objects.create(
name="test-full",
slug="test-full",
designation=FlowDesignation.AUTHENTICATION,
)
# Stage 1 is an identification stage
ident_stage = IdentificationStage.objects.create(
name="ident",
user_fields=[UserFields.USERNAME],
)
FlowStageBinding.objects.create(
target=flow,
stage=ident_stage,
order=1,
invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT,
)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy2"), order=1
)
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertJSONEqual(
res.content,
{
"component": "ak-stage-identification",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
"password_fields": False,
"primary_action": "Log in",
"sources": [],
"user_fields": ["username"],
},
)
ins = self.client.get(
reverse("authentik_api:flow-inspector", kwargs={"flow_slug": flow.slug}),
)
content = loads(ins.content)
self.assertEqual(content["is_completed"], False)
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "ident")
self.assertEqual(
content["current_plan"]["next_planned_stage"]["stage_obj"]["name"], "dummy2"
)
self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": "akadmin"},
follow=True,
)
ins = self.client.get(
reverse("authentik_api:flow-inspector", kwargs={"flow_slug": flow.slug}),
)
content = loads(ins.content)
self.assertEqual(content["is_completed"], False)
self.assertEqual(content["plans"][0]["current_stage"]["stage_obj"]["name"], "ident")
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "dummy2")
self.assertEqual(
content["current_plan"]["plan_context"]["pending_user"]["username"], "akadmin"
)

View File

@ -4,7 +4,7 @@ from typing import Callable, Type
from django.test import RequestFactory, TestCase
from authentik.flows.stage import StageView
from authentik.flows.views import FlowExecutorView
from authentik.flows.views.executor import FlowExecutorView
from authentik.lib.utils.reflection import all_subclasses

View File

@ -4,7 +4,7 @@ from django.urls import reverse
from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.flows.views.executor import SESSION_KEY_PLAN
class TestHelperView(TestCase):

View File

@ -2,7 +2,7 @@
from django.urls import path
from authentik.flows.models import FlowDesignation
from authentik.flows.views import CancelView, ConfigureFlowInitView, ToDefaultFlow
from authentik.flows.views.executor import CancelView, ConfigureFlowInitView, ToDefaultFlow
urlpatterns = [
path(

View File

View File

@ -1,4 +1,5 @@
"""authentik multi-stage authentication engine"""
from copy import deepcopy
from traceback import format_tb
from typing import Any, Optional
@ -52,6 +53,7 @@ NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN = "authentik_flows_plan"
SESSION_KEY_APPLICATION_PRE = "authentik_flows_application_pre"
SESSION_KEY_GET = "authentik_flows_get"
SESSION_KEY_HISTORY = "authentik_flows_history"
def challenge_types():
@ -140,6 +142,7 @@ class FlowExecutorView(APIView):
# Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan:
request.session[SESSION_KEY_HISTORY] = []
self._logger.debug("f(exec): No active Plan found, initiating planner")
try:
self.plan = self._initiate_plan()
@ -321,6 +324,7 @@ class FlowExecutorView(APIView):
"f(exec): Stage ok",
stage_class=class_to_path(self.current_stage_view.__class__),
)
self.request.session.get(SESSION_KEY_HISTORY, []).append(deepcopy(self.plan))
self.plan.pop()
self.request.session[SESSION_KEY_PLAN] = self.plan
if self.plan.bindings:
@ -368,6 +372,10 @@ class FlowExecutorView(APIView):
SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN,
SESSION_KEY_GET,
# We don't delete the history on purpose, as a user might
# still be inspecting it.
# It's only deleted on a fresh executions
# SESSION_KEY_HISTORY,
]
for key in keys_to_delete:
if key in self.request.session:

View File

@ -0,0 +1,119 @@
"""Flow Inspector"""
from hashlib import sha256
from typing import Any
from django.conf import settings
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.fields import BooleanField, ListField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.api.utils import PassiveSerializer
from authentik.events.utils import sanitize_dict
from authentik.flows.api.bindings import FlowStageBindingSerializer
from authentik.flows.models import Flow
from authentik.flows.planner import FlowPlan
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
class FlowInspectorPlanSerializer(PassiveSerializer):
"""Serializer for an active FlowPlan"""
current_stage = SerializerMethodField()
next_planned_stage = SerializerMethodField(required=False)
plan_context = SerializerMethodField()
session_id = SerializerMethodField()
def get_current_stage(self, plan: FlowPlan) -> FlowStageBindingSerializer:
"""Get the current stage"""
return FlowStageBindingSerializer(instance=plan.bindings[0]).data
def get_next_planned_stage(self, plan: FlowPlan) -> FlowStageBindingSerializer:
"""Get the next planned stage"""
if len(plan.bindings) < 2:
return FlowStageBindingSerializer().data
return FlowStageBindingSerializer(instance=plan.bindings[1]).data
def get_plan_context(self, plan: FlowPlan) -> dict[str, Any]:
"""Get the plan's context, sanitized"""
return sanitize_dict(plan.context)
# pylint: disable=unused-argument
def get_session_id(self, plan: FlowPlan) -> str:
"""Get a unique session ID"""
request: Request = self.context["request"]
return sha256(
f"{request._request.session.session_key}-{settings.SECRET_KEY}".encode("ascii")
).hexdigest()
class FlowInspectionSerializer(PassiveSerializer):
"""Serializer for inspect endpoint"""
plans = ListField(child=FlowInspectorPlanSerializer())
current_plan = FlowInspectorPlanSerializer(required=False)
is_completed = BooleanField()
@method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowInspectorView(APIView):
"""Flow inspector API"""
permission_classes = [IsAdminUser]
flow: Flow
_logger: BoundLogger
def setup(self, request: HttpRequest, flow_slug: str):
super().setup(request, flow_slug=flow_slug)
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
self._logger = get_logger().bind(flow_slug=flow_slug)
# pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
if SESSION_KEY_HISTORY not in self.request.session:
return HttpResponse(status=400)
return super().dispatch(request, flow_slug=flow_slug)
@extend_schema(
responses={
200: FlowInspectionSerializer(),
400: OpenApiResponse(
description="No flow plan in session."
), # This error can be raised by the email stage
},
request=OpenApiTypes.NONE,
operation_id="flows_inspector_get",
)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Get current flow state and record it"""
plans = []
for plan in request.session[SESSION_KEY_HISTORY]:
plan_serializer = FlowInspectorPlanSerializer(
instance=plan, context={"request": request}
)
plans.append(plan_serializer.data)
is_completed = False
if SESSION_KEY_PLAN in request.session:
current_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
else:
current_plan = request.session[SESSION_KEY_HISTORY][-1]
is_completed = True
current_serializer = FlowInspectorPlanSerializer(
instance=current_plan, context={"request": request}
)
response = {
"plans": plans,
"current_plan": current_serializer.data,
"is_completed": is_completed,
}
return Response(response)

View File

@ -54,7 +54,7 @@ outposts:
# %(type)s: Outpost type; proxy, ldap, etc
# %(version)s: Current version; 2021.4.1
# %(build_hash)s: Build hash if you're running a beta version
docker_image_base: "ghcr.io/goauthentik/%(type)s:%(version)s"
container_image_base: env://AUTHENTIK_OUTPOSTS__DOCKER_IMAGE_BASE?goauthentik.io/%(type)s:%(version)s
disable_update_check: false
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar

View File

@ -13,4 +13,4 @@ class AuthentikManagedConfig(AppConfig):
from authentik.managed.tasks import managed_reconcile
# pyright: reportGeneralTypeIssues=false
managed_reconcile() # pylint: disable=no-value-for-parameter
managed_reconcile.delay() # pylint: disable=no-value-for-parameter

View File

@ -2,11 +2,17 @@
from django.db import DatabaseError
from authentik.core.tasks import CELERY_APP
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.managed.manager import ObjectManager
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def managed_reconcile(self: MonitoredTask):
"""Run ObjectManager to ensure objects are up-to-date"""
try:

View File

@ -69,7 +69,10 @@ class BaseController:
def get_container_image(self) -> str:
"""Get container image to use for this outpost"""
image_name_template: str = CONFIG.y("outposts.docker_image_base")
if self.outpost.config.container_image is not None:
return self.outpost.config.container_image
image_name_template: str = CONFIG.y("outposts.container_image_base")
return image_name_template % {
"type": self.outpost.type,
"version": __version__,

View File

@ -81,7 +81,7 @@ class DockerController(BaseController):
return False
for port in self.deployment_ports:
key = f"{port.inner_port or port.port}/{port.protocol.lower()}"
if key not in container.ports:
if not container.ports.get(key, None):
return True
host_matching = False
for host_port in container.ports[key]:
@ -90,14 +90,24 @@ class DockerController(BaseController):
return True
return False
def try_pull_image(self):
"""Try to pull the image needed for this outpost based on the CONFIG
`outposts.container_image_base`, but fall back to known-good images"""
image = self.get_container_image()
try:
self.client.images.pull(image)
except DockerException:
image = f"goauthentik.io/{self.outpost.type}:latest"
self.client.images.pull(image)
return image
def _get_container(self) -> tuple[Container, bool]:
container_name = f"authentik-proxy-{self.outpost.uuid.hex}"
try:
return self.client.containers.get(container_name), False
except NotFound:
self.logger.info("(Re-)creating container...")
image_name = self.get_container_image()
self.client.images.pull(image_name)
image_name = self.try_pull_image()
container_args = {
"image": image_name,
"name": container_name,
@ -134,12 +144,12 @@ class DockerController(BaseController):
return None
# Check if the container is out of date, delete it and retry
if len(container.image.tags) > 0:
tag: str = container.image.tags[0]
if tag != self.get_container_image():
should_image = self.try_pull_image()
if should_image not in container.image.tags:
self.logger.info(
"Container has mismatched image, re-creating...",
has=tag,
should=self.get_container_image(),
has=container.image.tags,
should=should_image,
)
self.down()
return self.up(depth + 1)

View File

@ -1,5 +1,5 @@
"""Base Kubernetes Reconciler"""
from typing import TYPE_CHECKING, Generic, TypeVar
from typing import TYPE_CHECKING, Generic, Optional, TypeVar
from django.utils.text import slugify
from kubernetes.client import V1ObjectMeta
@ -70,21 +70,34 @@ class KubernetesObjectReconciler(Generic[T]):
raise exc
else:
self.reconcile(current, reference)
except NeedsRecreate:
self.logger.debug("Recreate requested")
if current:
self.logger.debug("Deleted old")
self.delete(current)
else:
self.logger.debug("No old found, creating")
self.logger.debug("Creating")
self.create(reference)
except NeedsUpdate:
self.logger.debug("Updating")
self.update(current, reference)
try:
self.update(current, reference)
self.logger.debug("Updating")
except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 422:
self.logger.debug("Failed to update current, triggering re-create")
self._recreate(current=current, reference=reference)
return
self.logger.debug("Other unhandled error", exc=exc)
raise exc
except NeedsRecreate:
self._recreate(current=current, reference=reference)
else:
self.logger.debug("Object is up-to-date.")
def _recreate(self, reference: T, current: Optional[T] = None):
"""Recreate object"""
self.logger.debug("Recreate requested")
if current:
self.logger.debug("Deleted old")
self.delete(current)
else:
self.logger.debug("No old found, creating")
self.logger.debug("Creating")
self.create(reference)
def down(self):
"""Delete object if found"""
if self.noop:
@ -138,6 +151,8 @@ class KubernetesObjectReconciler(Generic[T]):
"app.kubernetes.io/version": __version__,
"app.kubernetes.io/managed-by": "goauthentik.io",
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
},
**kwargs,
)

View File

@ -1,6 +1,7 @@
"""Kubernetes Deployment Reconciler"""
from typing import TYPE_CHECKING
from django.utils.text import slugify
from kubernetes.client import (
AppsV1Api,
V1Container,
@ -11,6 +12,7 @@ from kubernetes.client import (
V1EnvVarSource,
V1LabelSelector,
V1ObjectMeta,
V1ObjectReference,
V1PodSpec,
V1PodTemplateSpec,
V1SecretKeySelector,
@ -56,6 +58,8 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
"app.kubernetes.io/name": "authentik-outpost",
"app.kubernetes.io/managed-by": "goauthentik.io",
"goauthentik.io/outpost-uuid": self.controller.outpost.uuid.hex,
"goauthentik.io/outpost-name": slugify(self.controller.outpost.name),
"goauthentik.io/outpost-type": str(self.controller.outpost.type),
}
def get_reference_object(self) -> V1Deployment:
@ -72,6 +76,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
)
meta = self.get_object_meta(name=self.name)
image_name = self.controller.get_container_image()
image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets
return V1Deployment(
metadata=meta,
spec=V1DeploymentSpec(
@ -80,6 +85,9 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
template=V1PodTemplateSpec(
metadata=V1ObjectMeta(labels=self.get_pod_meta()),
spec=V1PodSpec(
image_pull_secrets=[
V1ObjectReference(name=secret) for secret in image_pull_secrets
],
containers=[
V1Container(
name=str(self.outpost.type),
@ -124,7 +132,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
),
],
)
]
],
),
),
),

View File

@ -0,0 +1,340 @@
# Generated by Django 3.2.8 on 2021-10-10 16:18
import uuid
import django.db.models.deletion
from django.apps.registry import Apps
from django.core.exceptions import FieldError
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.lib.models
import authentik.outposts.models
def fix_missing_token_identifier(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
User = apps.get_model("authentik_core", "User")
Token = apps.get_model("authentik_core", "Token")
from authentik.outposts.models import Outpost
for outpost in Outpost.objects.using(schema_editor.connection.alias).all().only("pk"):
user_identifier = outpost.user_identifier
users = User.objects.filter(username=user_identifier)
if not users.exists():
continue
tokens = Token.objects.filter(user=users.first())
for token in tokens:
if token.identifier != outpost.token_identifier:
token.identifier = outpost.token_identifier
token.save()
def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Outpost = apps.get_model("authentik_outposts", "Outpost")
DockerServiceConnection = apps.get_model("authentik_outposts", "DockerServiceConnection")
KubernetesServiceConnection = apps.get_model(
"authentik_outposts", "KubernetesServiceConnection"
)
docker = DockerServiceConnection.objects.filter(local=True).first()
k8s = KubernetesServiceConnection.objects.filter(local=True).first()
try:
for outpost in Outpost.objects.using(db_alias).all().exclude(deployment_type="custom"):
if outpost.deployment_type == "kubernetes":
outpost.service_connection = k8s
elif outpost.deployment_type == "docker":
outpost.service_connection = docker
outpost.save()
except FieldError:
# This is triggered during e2e tests when this function is called on an already-upgraded
# schema
pass
def remove_pb_prefix_users(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
alias = schema_editor.connection.alias
User = apps.get_model("authentik_core", "User")
Outpost = apps.get_model("authentik_outposts", "Outpost")
for outpost in Outpost.objects.using(alias).all():
matching = User.objects.using(alias).filter(username=f"pb-outpost-{outpost.uuid.hex}")
if matching.exists():
matching.delete()
def update_config_prefix(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
alias = schema_editor.connection.alias
Outpost = apps.get_model("authentik_outposts", "Outpost")
for outpost in Outpost.objects.using(alias).all():
config = outpost._config
for key in list(config):
if "passbook" in key:
new_key = key.replace("passbook", "authentik")
config[new_key] = config[key]
del config[key]
outpost._config = config
outpost.save()
class Migration(migrations.Migration):
replaces = [
("authentik_outposts", "0001_initial"),
("authentik_outposts", "0002_auto_20200826_1306"),
("authentik_outposts", "0003_auto_20200827_2108"),
("authentik_outposts", "0004_auto_20200830_1056"),
("authentik_outposts", "0005_auto_20200909_1733"),
("authentik_outposts", "0006_auto_20201003_2239"),
("authentik_outposts", "0007_remove_outpost_channels"),
("authentik_outposts", "0008_auto_20201014_1547"),
("authentik_outposts", "0009_fix_missing_token_identifier"),
("authentik_outposts", "0010_service_connection"),
("authentik_outposts", "0011_docker_tls_auth"),
("authentik_outposts", "0012_service_connection_non_unique"),
("authentik_outposts", "0013_auto_20201203_2009"),
("authentik_outposts", "0014_auto_20201213_1407"),
("authentik_outposts", "0015_auto_20201224_1206"),
("authentik_outposts", "0016_alter_outpost_type"),
("authentik_outposts", "0017_outpost_managed"),
]
initial = True
dependencies = [
("authentik_core", "0014_auto_20201018_1158"),
("authentik_core", "0016_auto_20201202_2234"),
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_core", "0008_auto_20200824_1532"),
]
operations = [
migrations.CreateModel(
name="Outpost",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("providers", models.ManyToManyField(to="authentik_core.Provider")),
(
"_config",
models.JSONField(default=authentik.outposts.models.default_outpost_config),
),
("type", models.TextField(choices=[("proxy", "Proxy")], default="proxy")),
(
"deployment_type",
models.TextField(
choices=[
("kubernetes", "Kubernetes"),
("docker", "Docker"),
("custom", "Custom"),
],
default="custom",
help_text="Select between authentik-managed deployment types or a custom deployment.",
),
),
],
),
migrations.RunPython(
code=fix_missing_token_identifier,
),
migrations.CreateModel(
name="OutpostServiceConnection",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
(
"local",
models.BooleanField(
default=False,
help_text="If enabled, use the local connection. Required Docker socket/Kubernetes Integration",
unique=True,
),
),
],
),
migrations.CreateModel(
name="DockerServiceConnection",
fields=[
(
"outpostserviceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_outposts.outpostserviceconnection",
),
),
("url", models.TextField()),
("tls", models.BooleanField()),
],
bases=("authentik_outposts.outpostserviceconnection",),
),
migrations.CreateModel(
name="KubernetesServiceConnection",
fields=[
(
"outpostserviceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_outposts.outpostserviceconnection",
),
),
("kubeconfig", models.JSONField()),
],
bases=("authentik_outposts.outpostserviceconnection",),
),
migrations.AddField(
model_name="outpost",
name="service_connection",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Select Service-Connection authentik should use to manage this outpost. Leave empty if authentik should not handle the deployment.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_outposts.outpostserviceconnection",
),
),
migrations.RunPython(
code=migrate_to_service_connection,
),
migrations.RemoveField(
model_name="outpost",
name="deployment_type",
),
migrations.AlterModelOptions(
name="dockerserviceconnection",
options={
"verbose_name": "Docker Service-Connection",
"verbose_name_plural": "Docker Service-Connections",
},
),
migrations.AlterModelOptions(
name="kubernetesserviceconnection",
options={
"verbose_name": "Kubernetes Service-Connection",
"verbose_name_plural": "Kubernetes Service-Connections",
},
),
migrations.AlterField(
model_name="outpost",
name="service_connection",
field=authentik.lib.models.InheritanceForeignKey(
blank=True,
default=None,
help_text="Select Service-Connection authentik should use to manage this outpost. Leave empty if authentik should not handle the deployment.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_outposts.outpostserviceconnection",
),
),
migrations.AlterModelOptions(
name="outpostserviceconnection",
options={
"verbose_name": "Outpost Service-Connection",
"verbose_name_plural": "Outpost Service-Connections",
},
),
migrations.AlterField(
model_name="kubernetesserviceconnection",
name="kubeconfig",
field=models.JSONField(
default=None,
help_text="Paste your kubeconfig here. authentik will automatically use the currently selected context.",
),
preserve_default=False,
),
migrations.RemoveField(
model_name="dockerserviceconnection",
name="tls",
),
migrations.AddField(
model_name="dockerserviceconnection",
name="tls_authentication",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Certificate/Key used for authentication. Can be left empty for no authentication.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
related_name="+",
to="authentik_crypto.certificatekeypair",
),
),
migrations.AddField(
model_name="dockerserviceconnection",
name="tls_verification",
field=models.ForeignKey(
blank=True,
default=None,
help_text="CA which the endpoint's Certificate is verified against. Can be left empty for no validation.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
related_name="+",
to="authentik_crypto.certificatekeypair",
),
),
migrations.AlterField(
model_name="outpostserviceconnection",
name="local",
field=models.BooleanField(
default=False,
help_text="If enabled, use the local connection. Required Docker socket/Kubernetes Integration",
),
),
migrations.RunPython(
code=remove_pb_prefix_users,
),
migrations.RunPython(
code=update_config_prefix,
),
migrations.AlterField(
model_name="dockerserviceconnection",
name="url",
field=models.TextField(
help_text="Can be in the format of 'unix://<path>' when connecting to a local docker daemon, or 'https://<hostname>:2376' when connecting to a remote system."
),
),
migrations.AlterField(
model_name="kubernetesserviceconnection",
name="kubeconfig",
field=models.JSONField(
blank=True,
help_text="Paste your kubeconfig here. authentik will automatically use the currently selected context.",
),
),
migrations.AlterField(
model_name="outpost",
name="type",
field=models.TextField(choices=[("proxy", "Proxy"), ("ldap", "Ldap")], default="proxy"),
),
migrations.AddField(
model_name="outpost",
name="managed",
field=models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
]

View File

@ -74,12 +74,15 @@ class OutpostConfig:
docker_network: Optional[str] = field(default=None)
docker_map_ports: bool = field(default=True)
container_image: Optional[str] = field(default=None)
kubernetes_replicas: int = field(default=1)
kubernetes_namespace: str = field(default_factory=get_namespace)
kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls")
kubernetes_service_type: str = field(default="ClusterIP")
kubernetes_disabled_components: list[str] = field(default_factory=list)
kubernetes_image_pull_secrets: Optional[list[str]] = field(default_factory=list)
class OutpostModel(Model):

View File

@ -17,7 +17,12 @@ from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
from structlog.stdlib import get_logger
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.lib.utils.reflection import path_to_class
from authentik.outposts.controllers.base import BaseController, ControllerException
from authentik.outposts.models import (
@ -71,6 +76,7 @@ def outpost_service_connection_state(connection_pk: Any):
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def outpost_service_connection_monitor(self: MonitoredTask):
"""Regularly check the state of Outpost Service Connections"""
connections = OutpostServiceConnection.objects.all()
@ -120,6 +126,7 @@ def outpost_controller(
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def outpost_token_ensurer(self: MonitoredTask):
"""Periodically ensure that all Outposts have valid Service Accounts
and Tokens"""

View File

@ -2,6 +2,8 @@
from typing import OrderedDict
from django.core.exceptions import ObjectDoesNotExist
from django_filters.filters import BooleanFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField, ValidationError
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
@ -96,6 +98,22 @@ class PolicyBindingSerializer(ModelSerializer):
return attrs
class PolicyBindingFilter(FilterSet):
"""Filter for PolicyBindings"""
target_in = ModelMultipleChoiceFilter(
field_name="target__pbm_uuid",
to_field_name="pbm_uuid",
queryset=PolicyBindingModel.objects.select_subclasses(),
)
policy__isnull = BooleanFilter("policy", "isnull")
class Meta:
model = PolicyBinding
fields = ["policy", "policy__isnull", "target", "target_in", "enabled", "order", "timeout"]
class PolicyBindingViewSet(UsedByMixin, ModelViewSet):
"""PolicyBinding Viewset"""
@ -105,5 +123,6 @@ class PolicyBindingViewSet(UsedByMixin, ModelViewSet):
.prefetch_related("policy")
) # prefetching policy so we resolve the subclass
serializer_class = PolicyBindingSerializer
filterset_fields = ["policy", "target", "enabled", "order", "timeout"]
search_fields = ["policy__name"]
filterset_class = PolicyBindingFilter
ordering = ["target", "order"]

View File

@ -0,0 +1,173 @@
# Generated by Django 3.2.8 on 2021-10-10 16:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [
("authentik_policies_event_matcher", "0001_initial"),
("authentik_policies_event_matcher", "0002_auto_20201230_2046"),
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
("authentik_policies_event_matcher", "0004_auto_20210112_2158"),
("authentik_policies_event_matcher", "0005_auto_20210202_1821"),
("authentik_policies_event_matcher", "0006_auto_20210203_1134"),
("authentik_policies_event_matcher", "0007_auto_20210209_1657"),
("authentik_policies_event_matcher", "0008_auto_20210213_1640"),
("authentik_policies_event_matcher", "0009_auto_20210215_2159"),
("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
("authentik_policies_event_matcher", "0011_auto_20210302_0856"),
("authentik_policies_event_matcher", "0012_auto_20210323_1339"),
("authentik_policies_event_matcher", "0013_alter_eventmatcherpolicy_app"),
("authentik_policies_event_matcher", "0014_alter_eventmatcherpolicy_app"),
("authentik_policies_event_matcher", "0015_alter_eventmatcherpolicy_app"),
("authentik_policies_event_matcher", "0016_alter_eventmatcherpolicy_action"),
("authentik_policies_event_matcher", "0017_alter_eventmatcherpolicy_action"),
("authentik_policies_event_matcher", "0018_alter_eventmatcherpolicy_action"),
]
initial = True
dependencies = [
("authentik_policies", "0004_policy_execution_logging"),
]
operations = [
migrations.CreateModel(
name="EventMatcherPolicy",
fields=[
(
"policy_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_policies.policy",
),
),
(
"action",
models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
help_text="Match created events with this action type. When left empty, all action types will be matched.",
),
),
(
"client_ip",
models.TextField(
blank=True,
help_text="Matches Event's Client IP (strict matching, for network matching use an Expression Policy)",
),
),
(
"app",
models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.ldap", "authentik Providers.LDAP"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.plex", "authentik Sources.Plex"),
("authentik.sources.saml", "authentik Sources.SAML"),
(
"authentik.stages.authenticator_duo",
"authentik Stages.Authenticator.Duo",
),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
(
"authentik.stages.authenticator_totp",
"authentik Stages.Authenticator.TOTP",
),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.deny", "authentik Stages.Deny"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
("authentik.stages.identification", "authentik Stages.Identification"),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
],
options={
"verbose_name": "Event Matcher Policy",
"verbose_name_plural": "Event Matcher Policies",
},
bases=("authentik_policies.policy",),
),
]

View File

@ -0,0 +1,79 @@
# Generated by Django 3.2.8 on 2021-10-09 17:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0018_alter_eventmatcherpolicy_action"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.crypto", "authentik Crypto"),
("authentik.events", "authentik Events"),
("authentik.flows", "authentik Flows"),
("authentik.lib", "authentik lib"),
("authentik.outposts", "authentik Outpost"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
("authentik.policies.event_matcher", "authentik Policies.Event Matcher"),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.policies", "authentik Policies"),
("authentik.providers.ldap", "authentik Providers.LDAP"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.plex", "authentik Sources.Plex"),
("authentik.sources.saml", "authentik Sources.SAML"),
("authentik.stages.authenticator_duo", "authentik Stages.Authenticator.Duo"),
("authentik.stages.authenticator_sms", "authentik Stages.Authenticator.SMS"),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
("authentik.stages.authenticator_totp", "authentik Stages.Authenticator.TOTP"),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.deny", "authentik Stages.Deny"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
("authentik.stages.identification", "authentik Stages.Identification"),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -65,6 +65,7 @@ class TestPasswordPolicyFlow(APITestCase):
"placeholder": "PASSWORD_PLACEHOLDER",
"required": True,
"type": "password",
"sub_text": "",
}
],
"flow_info": {

View File

@ -2,7 +2,12 @@
from django.core.cache import cache
from structlog.stdlib import get_logger
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.policies.reputation.models import IPReputation, UserReputation
from authentik.policies.reputation.signals import CACHE_KEY_IP_PREFIX, CACHE_KEY_USER_PREFIX
from authentik.root.celery import CELERY_APP
@ -11,6 +16,7 @@ LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def save_ip_reputation(self: MonitoredTask):
"""Save currently cached reputation to database"""
objects_to_update = []
@ -24,6 +30,7 @@ def save_ip_reputation(self: MonitoredTask):
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def save_user_reputation(self: MonitoredTask):
"""Save currently cached reputation to database"""
objects_to_update = []

View File

@ -10,7 +10,7 @@ from django.views.generic.base import View
from structlog.stdlib import get_logger
from authentik.core.models import Application, Provider, User
from authentik.flows.views import SESSION_KEY_APPLICATION_PRE
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.engine import PolicyEngine

View File

@ -0,0 +1,128 @@
# Generated by Django 3.2.8 on 2021-10-10 16:24
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
import authentik.lib.utils.time
scope_uid_map = {
"openid": "goauthentik.io/providers/oauth2/scope-openid",
"email": "goauthentik.io/providers/oauth2/scope-email",
"profile": "goauthentik.io/providers/oauth2/scope-profile",
"ak_proxy": "goauthentik.io/providers/proxy/scope-proxy",
}
def set_managed_flag(apps: Apps, schema_editor):
ScopeMapping = apps.get_model("authentik_providers_oauth2", "ScopeMapping")
db_alias = schema_editor.connection.alias
for mapping in ScopeMapping.objects.using(db_alias).filter(name__startswith="Autogenerated "):
mapping.managed = scope_uid_map[mapping.scope_name]
mapping.save()
class Migration(migrations.Migration):
replaces = [
("authentik_providers_oauth2", "0007_auto_20201016_1107"),
("authentik_providers_oauth2", "0008_oauth2provider_issuer_mode"),
("authentik_providers_oauth2", "0009_remove_oauth2provider_response_type"),
("authentik_providers_oauth2", "0010_auto_20201227_1804"),
("authentik_providers_oauth2", "0011_managed"),
("authentik_providers_oauth2", "0012_oauth2provider_access_code_validity"),
("authentik_providers_oauth2", "0013_alter_authorizationcode_nonce"),
("authentik_providers_oauth2", "0014_alter_oauth2provider_rsa_key"),
("authentik_providers_oauth2", "0015_auto_20210703_1313"),
("authentik_providers_oauth2", "0016_alter_authorizationcode_nonce"),
("authentik_providers_oauth2", "0017_alter_oauth2provider_token_validity"),
]
dependencies = [
("authentik_core", "0017_managed"),
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_providers_oauth2", "0006_remove_oauth2provider_name"),
]
operations = [
migrations.AlterModelOptions(
name="refreshtoken",
options={"verbose_name": "OAuth2 Token", "verbose_name_plural": "OAuth2 Tokens"},
),
migrations.AddField(
model_name="oauth2provider",
name="issuer_mode",
field=models.TextField(
choices=[
("global", "Same identifier is used for all providers"),
(
"per_provider",
"Each provider has a different issuer, based on the application slug.",
),
],
default="per_provider",
help_text="Configure how the issuer field of the ID Token should be filled.",
),
),
migrations.RemoveField(
model_name="oauth2provider",
name="response_type",
),
migrations.AlterField(
model_name="refreshtoken",
name="access_token",
field=models.TextField(verbose_name="Access Token"),
),
migrations.RunPython(
code=set_managed_flag,
),
migrations.AddField(
model_name="oauth2provider",
name="access_code_validity",
field=models.TextField(
default="minutes=1",
help_text="Access codes not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
migrations.AlterField(
model_name="authorizationcode",
name="nonce",
field=models.TextField(blank=True, default="", verbose_name="Nonce"),
),
migrations.AlterField(
model_name="oauth2provider",
name="rsa_key",
field=models.ForeignKey(
help_text="Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
verbose_name="RSA Key",
),
),
migrations.AddField(
model_name="authorizationcode",
name="revoked",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="refreshtoken",
name="revoked",
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name="authorizationcode",
name="nonce",
field=models.TextField(default=None, null=True, verbose_name="Nonce"),
),
migrations.AlterField(
model_name="oauth2provider",
name="token_validity",
field=models.TextField(
default="days=30",
help_text="Tokens not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
]

View File

@ -23,7 +23,7 @@ from authentik.flows.planner import (
FlowPlanner,
)
from authentik.flows.stage import StageView
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message

View File

@ -3,15 +3,17 @@ from typing import TYPE_CHECKING
from urllib.parse import urlparse
from kubernetes.client import (
NetworkingV1beta1Api,
NetworkingV1beta1HTTPIngressPath,
NetworkingV1beta1HTTPIngressRuleValue,
NetworkingV1beta1Ingress,
NetworkingV1beta1IngressBackend,
NetworkingV1beta1IngressSpec,
NetworkingV1beta1IngressTLS,
NetworkingV1Api,
V1HTTPIngressPath,
V1HTTPIngressRuleValue,
V1Ingress,
V1IngressSpec,
V1IngressTLS,
V1ServiceBackendPort,
)
from kubernetes.client.models.networking_v1beta1_ingress_rule import NetworkingV1beta1IngressRule
from kubernetes.client.models.v1_ingress_backend import V1IngressBackend
from kubernetes.client.models.v1_ingress_rule import V1IngressRule
from kubernetes.client.models.v1_ingress_service_backend import V1IngressServiceBackend
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
@ -22,14 +24,14 @@ if TYPE_CHECKING:
from authentik.outposts.controllers.kubernetes import KubernetesController
class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
"""Kubernetes Ingress Reconciler"""
def __init__(self, controller: "KubernetesController") -> None:
super().__init__(controller)
self.api = NetworkingV1beta1Api(controller.client)
self.api = NetworkingV1Api(controller.client)
def _check_annotations(self, reference: NetworkingV1beta1Ingress):
def _check_annotations(self, reference: V1Ingress):
"""Check that all annotations *we* set are correct"""
for key, value in self.get_ingress_annotations().items():
if key not in reference.metadata.annotations:
@ -37,7 +39,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
if reference.metadata.annotations[key] != value:
raise NeedsUpdate()
def reconcile(self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress):
def reconcile(self, current: V1Ingress, reference: V1Ingress):
super().reconcile(current, reference)
self._check_annotations(reference)
# Create a list of all expected host and tls hosts
@ -93,7 +95,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
annotations.update(self.controller.outpost.config.kubernetes_ingress_annotations)
return annotations
def get_reference_object(self) -> NetworkingV1beta1Ingress:
def get_reference_object(self) -> V1Ingress:
"""Get deployment object for outpost"""
meta = self.get_object_meta(
name=self.name,
@ -112,31 +114,37 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
ProxyMode.FORWARD_SINGLE,
ProxyMode.FORWARD_DOMAIN,
]:
rule = NetworkingV1beta1IngressRule(
rule = V1IngressRule(
host=external_host_name.hostname,
http=NetworkingV1beta1HTTPIngressRuleValue(
http=V1HTTPIngressRuleValue(
paths=[
NetworkingV1beta1HTTPIngressPath(
backend=NetworkingV1beta1IngressBackend(
service_name=self.name,
service_port="http",
V1HTTPIngressPath(
backend=V1IngressBackend(
service=V1IngressServiceBackend(
name=self.name,
port=V1ServiceBackendPort(name="http"),
),
),
path="/akprox",
path_type="ImplementationSpecific",
)
]
),
)
else:
rule = NetworkingV1beta1IngressRule(
rule = V1IngressRule(
host=external_host_name.hostname,
http=NetworkingV1beta1HTTPIngressRuleValue(
http=V1HTTPIngressRuleValue(
paths=[
NetworkingV1beta1HTTPIngressPath(
backend=NetworkingV1beta1IngressBackend(
service_name=self.name,
service_port="http",
V1HTTPIngressPath(
backend=V1IngressBackend(
service=V1IngressServiceBackend(
name=self.name,
port=V1ServiceBackendPort(name="http"),
),
),
path="/",
path_type="ImplementationSpecific",
)
]
),
@ -144,16 +152,16 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
rules.append(rule)
tls_config = None
if tls_hosts:
tls_config = NetworkingV1beta1IngressTLS(
tls_config = V1IngressTLS(
hosts=tls_hosts,
secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name,
)
return NetworkingV1beta1Ingress(
return V1Ingress(
metadata=meta,
spec=NetworkingV1beta1IngressSpec(rules=rules, tls=[tls_config]),
spec=V1IngressSpec(rules=rules, tls=[tls_config]),
)
def create(self, reference: NetworkingV1beta1Ingress):
def create(self, reference: V1Ingress):
if len(reference.spec.rules) < 1:
self.logger.debug("No hosts defined, not creating ingress.")
return None
@ -161,13 +169,13 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
self.namespace, reference, field_manager=FIELD_MANAGER
)
def delete(self, reference: NetworkingV1beta1Ingress):
def delete(self, reference: V1Ingress):
return self.api.delete_namespaced_ingress(reference.metadata.name, self.namespace)
def retrieve(self) -> NetworkingV1beta1Ingress:
def retrieve(self) -> V1Ingress:
return self.api.read_namespaced_ingress(self.name, self.namespace)
def update(self, current: NetworkingV1beta1Ingress, reference: NetworkingV1beta1Ingress):
def update(self, current: V1Ingress, reference: V1Ingress):
return self.api.patch_namespaced_ingress(
current.metadata.name,
self.namespace,

View File

@ -0,0 +1,180 @@
# Generated by Django 3.2.8 on 2021-10-12 15:39
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.lib.models
import authentik.providers.proxy.models
def migrate_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.providers.proxy.models import JWTAlgorithms, ProxyProvider
db_alias = schema_editor.connection.alias
for provider in ProxyProvider.objects.using(db_alias).filter(jwt_alg=JWTAlgorithms.RS256):
provider.set_oauth_defaults()
provider.save()
def migrate_mode(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.providers.proxy.models import ProxyMode
db_alias = schema_editor.connection.alias
ProxyProvider = apps.get_model("authentik_providers_proxy", "proxyprovider")
for provider in ProxyProvider.objects.using(db_alias).all():
if provider.forward_auth_mode:
provider.mode = ProxyMode.FORWARD_SINGLE
provider.save()
class Migration(migrations.Migration):
replaces = [
("authentik_providers_proxy", "0001_initial"),
("authentik_providers_proxy", "0002_proxyprovider_cookie_secret"),
("authentik_providers_proxy", "0003_proxyprovider_certificate"),
("authentik_providers_proxy", "0004_auto_20200913_1947"),
("authentik_providers_proxy", "0005_auto_20200914_1536"),
("authentik_providers_proxy", "0006_proxyprovider_skip_path_regex"),
("authentik_providers_proxy", "0007_auto_20200923_1017"),
("authentik_providers_proxy", "0008_auto_20200930_0810"),
("authentik_providers_proxy", "0009_auto_20201007_1721"),
("authentik_providers_proxy", "0010_auto_20201214_0942"),
("authentik_providers_proxy", "0011_proxyprovider_forward_auth_mode"),
("authentik_providers_proxy", "0012_proxyprovider_cookie_domain"),
("authentik_providers_proxy", "0013_mode"),
("authentik_providers_proxy", "0014_proxy_v2"),
]
initial = True
dependencies = [
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_providers_oauth2", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ProxyProvider",
fields=[
(
"oauth2provider_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_providers_oauth2.oauth2provider",
),
),
(
"internal_host",
models.TextField(
blank=True,
validators=[
authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))
],
),
),
(
"external_host",
models.TextField(
validators=[
authentik.lib.models.DomainlessURLValidator(schemes=("http", "https"))
]
),
),
(
"cookie_secret",
models.TextField(default=authentik.providers.proxy.models.get_cookie_secret),
),
(
"certificate",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
),
),
(
"skip_path_regex",
models.TextField(
blank=True,
default="",
help_text="Regular expressions for which authentication is not required. Each new line is interpreted as a new Regular Expression.",
),
),
(
"internal_host_ssl_validation",
models.BooleanField(
default=True,
help_text="Validate SSL Certificates of upstream servers",
verbose_name="Internal host SSL Validation",
),
),
(
"basic_auth_enabled",
models.BooleanField(
default=False,
help_text="Set a custom HTTP-Basic Authentication header based on values from authentik.",
verbose_name="Set HTTP-Basic Authentication",
),
),
(
"basic_auth_password_attribute",
models.TextField(
blank=True,
help_text="User/Group Attribute used for the password part of the HTTP-Basic Header.",
verbose_name="HTTP-Basic Password Key",
),
),
(
"basic_auth_user_attribute",
models.TextField(
blank=True,
help_text="User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
verbose_name="HTTP-Basic Username Key",
),
),
(
"forward_auth_mode",
models.BooleanField(
default=False,
help_text="Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host.",
),
),
("cookie_domain", models.TextField(blank=True, default="")),
(
"mode",
models.TextField(
choices=[
("proxy", "Proxy"),
("forward_single", "Forward Single"),
("forward_domain", "Forward Domain"),
],
default="proxy",
help_text="Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host.",
),
),
],
options={
"verbose_name": "Proxy Provider",
"verbose_name_plural": "Proxy Providers",
},
bases=("authentik_providers_oauth2.oauth2provider",),
),
migrations.RunPython(
code=migrate_mode,
),
migrations.RemoveField(
model_name="proxyprovider",
name="forward_auth_mode",
),
migrations.RunPython(
code=migrate_defaults,
),
]

View File

@ -0,0 +1,146 @@
# Generated by Django 3.2.8 on 2021-10-10 16:16
import django.db.models.deletion
from django.db import migrations, models
import authentik.lib.utils.time
class Migration(migrations.Migration):
replaces = [
("authentik_providers_saml", "0001_initial"),
("authentik_providers_saml", "0002_default_saml_property_mappings"),
("authentik_providers_saml", "0003_samlprovider_sp_binding"),
("authentik_providers_saml", "0004_auto_20200620_1950"),
("authentik_providers_saml", "0005_remove_samlprovider_processor_path"),
]
initial = True
dependencies = [
("authentik_crypto", "0001_initial"),
("authentik_core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="SAMLPropertyMapping",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.propertymapping",
),
),
("saml_name", models.TextField(verbose_name="SAML Name")),
("friendly_name", models.TextField(blank=True, default=None, null=True)),
],
options={
"verbose_name": "SAML Property Mapping",
"verbose_name_plural": "SAML Property Mappings",
},
bases=("authentik_core.propertymapping",),
),
migrations.CreateModel(
name="SAMLProvider",
fields=[
(
"provider_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.provider",
),
),
("name", models.TextField()),
("acs_url", models.URLField(verbose_name="ACS URL")),
("audience", models.TextField(default="")),
("issuer", models.TextField(help_text="Also known as EntityID")),
(
"assertion_valid_not_before",
models.TextField(
default="minutes=-5",
help_text="Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"assertion_valid_not_on_or_after",
models.TextField(
default="minutes=5",
help_text="Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"session_valid_not_on_or_after",
models.TextField(
default="minutes=86400",
help_text="Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"digest_algorithm",
models.CharField(
choices=[("sha1", "SHA1"), ("sha256", "SHA256")],
default="sha256",
max_length=50,
),
),
(
"signature_algorithm",
models.CharField(
choices=[
("rsa-sha1", "RSA-SHA1"),
("rsa-sha256", "RSA-SHA256"),
("ecdsa-sha256", "ECDSA-SHA256"),
("dsa-sha1", "DSA-SHA1"),
],
default="rsa-sha256",
max_length=50,
),
),
(
"require_signing",
models.BooleanField(
default=False,
help_text="Require Requests to be signed by an X509 Certificate. Must match the Certificate selected in `Singing Keypair`.",
),
),
(
"signing_kp",
models.ForeignKey(
default=None,
help_text="Singing is enabled upon selection of a Key Pair.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
verbose_name="Signing Keypair",
),
),
(
"sp_binding",
models.TextField(
choices=[("redirect", "Redirect"), ("post", "Post")],
default="redirect",
verbose_name="Service Prodier Binding",
),
),
],
options={
"verbose_name": "SAML Provider",
"verbose_name_plural": "SAML Providers",
},
bases=("authentik_core.provider",),
),
]

View File

@ -13,7 +13,7 @@ from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.policies.views import PolicyAccessView

View File

@ -37,7 +37,11 @@ class Command(BaseCommand):
duration = int(options.get("duration", 1))
_now = now()
expiry = _now + timedelta(days=duration * 365.2425)
user = User.objects.get(username=options.get("user"))
users = User.objects.filter(username=options.get("user"))
if not users.exists():
self.stderr.write(f"User '{options.get('user')}' not found.")
return
user = users.first()
token = Token.objects.create(
expires=expiry,
user=user,

View File

@ -93,12 +93,11 @@ INSTALLED_APPS = [
"django.contrib.humanize",
"authentik.admin",
"authentik.api",
"authentik.events",
"authentik.crypto",
"authentik.events",
"authentik.flows",
"authentik.outposts",
"authentik.lib",
"authentik.policies",
"authentik.outposts",
"authentik.policies.dummy",
"authentik.policies.event_matcher",
"authentik.policies.expiry",
@ -106,9 +105,10 @@ INSTALLED_APPS = [
"authentik.policies.hibp",
"authentik.policies.password",
"authentik.policies.reputation",
"authentik.providers.proxy",
"authentik.policies",
"authentik.providers.ldap",
"authentik.providers.oauth2",
"authentik.providers.proxy",
"authentik.providers.saml",
"authentik.recovery",
"authentik.sources.ldap",
@ -116,6 +116,7 @@ INSTALLED_APPS = [
"authentik.sources.plex",
"authentik.sources.saml",
"authentik.stages.authenticator_duo",
"authentik.stages.authenticator_sms",
"authentik.stages.authenticator_static",
"authentik.stages.authenticator_totp",
"authentik.stages.authenticator_validate",
@ -338,6 +339,7 @@ USE_L10N = True
USE_TZ = True
LOCALE_PATHS = ["./locale"]
# Celery settings
# Add a 10 minute timeout to all Celery tasks.
@ -446,7 +448,6 @@ structlog.configure_once(
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso", utc=False),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
@ -463,7 +464,6 @@ LOG_PRE_CHAIN = [
structlog.stdlib.add_logger_name,
structlog.processors.TimeStamper(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
]
LOGGING = {

View File

@ -17,8 +17,8 @@ class PytestTestRunner: # pragma: no cover
CONFIG.y_set("authentik.avatars", "none")
CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb")
CONFIG.y_set(
"outposts.docker_image_base",
f"beryju.org/authentik/outpost-%(type)s:{get_docker_tag()}",
"outposts.container_image_base",
f"goauthentik.io/dev-%(type)s:{get_docker_tag()}",
)
def run_tests(self, test_labels):

View File

@ -0,0 +1,248 @@
# Generated by Django 3.2.8 on 2021-10-10 16:26
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
import authentik.lib.models
def set_managed_flag(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model("authentik_sources_ldap", "LDAPPropertyMapping")
db_alias = schema_editor.connection.alias
field_to_uid = {
"name": "goauthentik.io/sources/ldap/default-name",
"email": "goauthentik.io/sources/ldap/default-mail",
"username": "goauthentik.io/sources/ldap/ms-samaccountname",
"attributes.upn": "goauthentik.io/sources/ldap/ms-userprincipalname",
"first_name": "goauthentik.io/sources/ldap/ms-givenName",
"last_name": "goauthentik.io/sources/ldap/ms-sn",
}
for mapping in LDAPPropertyMapping.objects.using(db_alias).filter(
name__startswith="Autogenerated "
):
mapping.managed = field_to_uid.get(mapping.object_field)
mapping.save()
def set_default_group_mappings(apps: Apps, schema_editor):
LDAPPropertyMapping = apps.get_model("authentik_sources_ldap", "LDAPPropertyMapping")
LDAPSource = apps.get_model("authentik_sources_ldap", "LDAPSource")
db_alias = schema_editor.connection.alias
for source in LDAPSource.objects.using(db_alias).all():
if source.property_mappings_group.exists():
continue
source.property_mappings_group.set(
LDAPPropertyMapping.objects.using(db_alias).filter(
managed="goauthentik.io/sources/ldap/default-name"
)
)
source.save()
class Migration(migrations.Migration):
replaces = [
("authentik_sources_ldap", "0001_initial"),
("authentik_sources_ldap", "0002_ldapsource_sync_users"),
("authentik_sources_ldap", "0003_default_ldap_property_mappings"),
("authentik_sources_ldap", "0004_auto_20200524_1146"),
("authentik_sources_ldap", "0005_auto_20200913_1947"),
("authentik_sources_ldap", "0006_auto_20200915_1919"),
("authentik_sources_ldap", "0007_ldapsource_sync_users_password"),
("authentik_sources_ldap", "0008_managed"),
("authentik_sources_ldap", "0009_auto_20210204_1834"),
("authentik_sources_ldap", "0010_auto_20210205_1027"),
("authentik_sources_ldap", "0011_ldapsource_property_mappings_group"),
("authentik_sources_ldap", "0012_auto_20210812_1703"),
]
initial = True
dependencies = [
("authentik_core", "0001_initial"),
("authentik_core", "0017_managed"),
]
operations = [
migrations.CreateModel(
name="LDAPPropertyMapping",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.propertymapping",
),
),
("object_field", models.TextField()),
],
options={
"verbose_name": "LDAP Property Mapping",
"verbose_name_plural": "LDAP Property Mappings",
},
bases=("authentik_core.propertymapping",),
),
migrations.CreateModel(
name="LDAPSource",
fields=[
(
"source_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.source",
),
),
(
"server_uri",
models.TextField(
validators=[
authentik.lib.models.DomainlessURLValidator(schemes=["ldap", "ldaps"])
],
verbose_name="Server URI",
),
),
("bind_cn", models.TextField(verbose_name="Bind CN")),
("bind_password", models.TextField()),
("start_tls", models.BooleanField(default=False, verbose_name="Enable Start TLS")),
("base_dn", models.TextField(verbose_name="Base DN")),
(
"additional_user_dn",
models.TextField(
blank=True,
help_text="Prepended to Base DN for User-queries.",
verbose_name="Addition User DN",
),
),
(
"additional_group_dn",
models.TextField(
blank=True,
help_text="Prepended to Base DN for Group-queries.",
verbose_name="Addition Group DN",
),
),
(
"user_object_filter",
models.TextField(
default="(objectCategory=Person)",
help_text="Consider Objects matching this filter to be Users.",
),
),
(
"user_group_membership_field",
models.TextField(
default="memberOf", help_text="Field which contains Groups of user."
),
),
(
"group_object_filter",
models.TextField(
default="(objectCategory=Group)",
help_text="Consider Objects matching this filter to be Groups.",
),
),
(
"object_uniqueness_field",
models.TextField(
default="objectSid", help_text="Field which contains a unique Identifier."
),
),
("sync_groups", models.BooleanField(default=True)),
(
"sync_parent_group",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.group",
),
),
("sync_users", models.BooleanField(default=True)),
(
"sync_users_password",
models.BooleanField(
default=True,
help_text="When a user changes their password, sync it back to LDAP. This can only be enabled on a single LDAP source.",
unique=True,
),
),
],
options={
"verbose_name": "LDAP Source",
"verbose_name_plural": "LDAP Sources",
},
bases=("authentik_core.source",),
),
migrations.RunPython(
code=set_managed_flag,
),
migrations.RemoveField(
model_name="ldapsource",
name="user_group_membership_field",
),
migrations.AddField(
model_name="ldapsource",
name="group_membership_field",
field=models.TextField(
default="member", help_text="Field which contains members of a group."
),
),
migrations.AlterField(
model_name="ldapsource",
name="group_object_filter",
field=models.TextField(
default="(objectClass=group)",
help_text="Consider Objects matching this filter to be Groups.",
),
),
migrations.AlterField(
model_name="ldapsource",
name="user_object_filter",
field=models.TextField(
default="(objectClass=person)",
help_text="Consider Objects matching this filter to be Users.",
),
),
migrations.AddField(
model_name="ldapsource",
name="property_mappings_group",
field=models.ManyToManyField(
blank=True,
default=None,
help_text="Property mappings used for group creation/updating.",
to="authentik_core.PropertyMapping",
),
),
migrations.RunPython(
code=set_default_group_mappings,
),
migrations.AlterField(
model_name="ldapsource",
name="bind_cn",
field=models.TextField(blank=True, verbose_name="Bind CN"),
),
migrations.AlterField(
model_name="ldapsource",
name="bind_password",
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name="ldapsource",
name="sync_users_password",
field=models.BooleanField(
default=True,
help_text="When a user changes their password, sync it back to LDAP. This can only be enabled on a single LDAP source.",
),
),
]

View File

@ -4,7 +4,7 @@ from re import split
from typing import Optional
import ldap3
import ldap3.core.exceptions
from ldap3.core.exceptions import LDAPAttributeError
from structlog.stdlib import get_logger
from authentik.core.models import User
@ -67,9 +67,9 @@ class LDAPPasswordChanger:
search_scope=ldap3.BASE,
attributes=["pwdProperties"],
)
except ldap3.core.exceptions.LDAPAttributeError:
root_attrs = list(root_attrs)[0]
except (LDAPAttributeError, KeyError, IndexError):
return False
root_attrs = list(root_attrs)[0]
raw_pwd_properties = root_attrs.get("attributes", {}).get("pwdProperties", None)
if raw_pwd_properties is None:
return False
@ -86,7 +86,10 @@ class LDAPPasswordChanger:
if not user_dn:
LOGGER.info(f"User has no {LDAP_DISTINGUISHED_NAME} set.")
return
self._source.connection.extend.microsoft.modify_password(user_dn, password)
try:
self._source.connection.extend.microsoft.modify_password(user_dn, password)
except LDAPAttributeError:
self._source.connection.extend.standard.modify_password(user_dn, new_password=password)
def _ad_check_password_existing(self, password: str, user_dn: str) -> bool:
"""Check if a password contains sAMAccount or displayName"""

View File

@ -1,16 +1,14 @@
"""Sync LDAP Users into authentik"""
from datetime import datetime
import ldap3
import ldap3.core.exceptions
from django.core.exceptions import FieldError
from django.db.utils import IntegrityError
from pytz import UTC
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
from authentik.sources.ldap.sync.vendor.ad import UserAccountControl
from authentik.sources.ldap.sync.vendor.freeipa import FreeIPA
from authentik.sources.ldap.sync.vendor.ms_ad import MicrosoftActiveDirectory
class UserLDAPSynchronizer(BaseLDAPSynchronizer):
@ -64,20 +62,6 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
else:
self._logger.debug("Synced User", user=ak_user.username, created=created)
user_count += 1
pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
if created or pwd_last_set >= ak_user.password_change_date:
self.message(f"'{ak_user.username}': Reset user's password")
self._logger.debug(
"Reset user's password",
user=ak_user.username,
created=created,
pwd_last_set=pwd_last_set,
)
ak_user.set_unusable_password()
ak_user.save()
if "userAccountControl" in attributes:
uac = UserAccountControl(attributes.get("userAccountControl"))
ak_user.is_active = UserAccountControl.ACCOUNTDISABLE not in uac
ak_user.save()
MicrosoftActiveDirectory(self._source).sync(attributes, ak_user, created)
FreeIPA(self._source).sync(attributes, ak_user, created)
return user_count

View File

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

View File

@ -0,0 +1,32 @@
"""FreeIPA specific"""
from datetime import datetime
from typing import Any
from pytz import UTC
from authentik.core.models import User
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
class FreeIPA(BaseLDAPSynchronizer):
"""FreeIPA-specific LDAP"""
def sync(self, attributes: dict[str, Any], user: User, created: bool):
self.check_pwd_last_set(attributes, user, created)
def check_pwd_last_set(self, attributes: dict[str, Any], user: User, created: bool):
"""Check krbLastPwdChange"""
if "krbLastPwdChange" not in attributes:
return
pwd_last_set: datetime = attributes.get("krbLastPwdChange", datetime.now())
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
if created or pwd_last_set >= user.password_change_date:
self.message(f"'{user.username}': Reset user's password")
self._logger.debug(
"Reset user's password",
user=user.username,
created=created,
pwd_last_set=pwd_last_set,
)
user.set_unusable_password()
user.save()

View File

@ -0,0 +1,75 @@
"""Active Directory specific"""
from datetime import datetime
from enum import IntFlag
from typing import Any
from pytz import UTC
from authentik.core.models import User
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
class UserAccountControl(IntFlag):
"""UserAccountControl attribute for Active directory users"""
# https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity
# /useraccountcontrol-manipulate-account-properties
SCRIPT = 1
ACCOUNTDISABLE = 2
HOMEDIR_REQUIRED = 8
LOCKOUT = 16
PASSWD_NOTREQD = 32
PASSWD_CANT_CHANGE = 64
ENCRYPTED_TEXT_PWD_ALLOWED = 128
TEMP_DUPLICATE_ACCOUNT = 256
NORMAL_ACCOUNT = 512
INTERDOMAIN_TRUST_ACCOUNT = 2048
WORKSTATION_TRUST_ACCOUNT = 4096
SERVER_TRUST_ACCOUNT = 8192
DONT_EXPIRE_PASSWORD = 65536
MNS_LOGON_ACCOUNT = 131072
SMARTCARD_REQUIRED = 262144
TRUSTED_FOR_DELEGATION = 524288
NOT_DELEGATED = 1048576
USE_DES_KEY_ONLY = 2097152
DONT_REQ_PREAUTH = 4194304
PASSWORD_EXPIRED = 8388608
TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
PARTIAL_SECRETS_ACCOUNT = 67108864
class MicrosoftActiveDirectory(BaseLDAPSynchronizer):
"""Microsoft-specific LDAP"""
def sync(self, attributes: dict[str, Any], user: User, created: bool):
self.ms_check_pwd_last_set(attributes, user, created)
self.ms_check_uac(attributes, user)
def ms_check_pwd_last_set(self, attributes: dict[str, Any], user: User, created: bool):
"""Check pwdLastSet"""
if "pwdLastSet" not in attributes:
return
pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
if created or pwd_last_set >= user.password_change_date:
self.message(f"'{user.username}': Reset user's password")
self._logger.debug(
"Reset user's password",
user=user.username,
created=created,
pwd_last_set=pwd_last_set,
)
user.set_unusable_password()
user.save()
def ms_check_uac(self, attributes: dict[str, Any], user: User):
"""Check userAccountControl"""
if "userAccountControl" not in attributes:
return
# Default from https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity
# /useraccountcontrol-manipulate-account-properties
uac_bit = attributes.get("userAccountControl", 512)
uac = UserAccountControl(uac_bit)
user.is_active = UserAccountControl.ACCOUNTDISABLE not in uac
user.save()

View File

@ -2,7 +2,7 @@
from ldap3 import MOCK_SYNC, OFFLINE_AD_2012_R2, Connection, Server
from authentik.sources.ldap.sync.vendor.ad import UserAccountControl
from authentik.sources.ldap.sync.vendor.ms_ad import UserAccountControl
def mock_ad_connection(password: str) -> Connection:

View File

@ -7,14 +7,15 @@ from structlog.stdlib import get_logger
LOGGER = get_logger()
AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.apple",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.discord",
"authentik.sources.oauth.types.facebook",
"authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.google",
"authentik.sources.oauth.types.oidc",
"authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.oidc",
]

View File

@ -1,6 +1,6 @@
"""OAuth Clients"""
from typing import Any, Optional
from urllib.parse import urlencode
from urllib.parse import quote, urlencode
from django.http import HttpRequest
from requests import Session
@ -58,7 +58,7 @@ class BaseOAuthClient:
args = self.get_redirect_args()
additional = parameters or {}
args.update(additional)
params = urlencode(args)
params = urlencode(args, quote_via=quote)
LOGGER.info("redirect args", **args)
authorization_url = self.source.type.authorization_url or ""
if self.source.type.urls_customizable and self.source.authorization_url:

View File

@ -20,10 +20,16 @@ class OAuth2Client(BaseOAuthClient):
"Accept": "application/json",
}
def get_request_arg(self, key: str, default: Optional[Any] = None) -> Any:
"""Depending on request type, get data from post or get"""
if self.request.method == "POST":
return self.request.POST.get(key, default)
return self.request.GET.get(key, default)
def check_application_state(self) -> bool:
"Check optional state parameter."
stored = self.request.session.get(self.session_key, None)
returned = self.request.GET.get("state", None)
returned = self.get_request_arg("state", None)
check = False
if stored is not None:
if returned is not None:
@ -38,23 +44,31 @@ class OAuth2Client(BaseOAuthClient):
"Generate state optional parameter."
return get_random_string(32)
def get_client_id(self) -> str:
"""Get client id"""
return self.source.consumer_key
def get_client_secret(self) -> str:
"""Get client secret"""
return self.source.consumer_secret
def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]:
"Fetch access token from callback request."
callback = self.request.build_absolute_uri(self.callback or self.request.path)
if not self.check_application_state():
LOGGER.warning("Application state check failed.")
return None
if "code" in self.request.GET:
args = {
"client_id": self.source.consumer_key,
"redirect_uri": callback,
"client_secret": self.source.consumer_secret,
"code": self.request.GET["code"],
"grant_type": "authorization_code",
}
else:
code = self.get_request_arg("code", None)
if not code:
LOGGER.warning("No code returned by the source")
return None
args = {
"client_id": self.get_client_id(),
"client_secret": self.get_client_secret(),
"redirect_uri": callback,
"code": code,
"grant_type": "authorization_code",
}
try:
access_token_url = self.source.type.access_token_url or ""
if self.source.type.urls_customizable and self.source.access_token_url:
@ -75,7 +89,7 @@ class OAuth2Client(BaseOAuthClient):
def get_redirect_args(self) -> dict[str, str]:
"Get request parameters for redirect url."
callback = self.request.build_absolute_uri(self.callback)
client_id: str = self.source.consumer_key
client_id: str = self.get_client_id()
args: dict[str, str] = {
"client_id": client_id,
"redirect_uri": callback,

View File

@ -2,7 +2,6 @@
from typing import TYPE_CHECKING, Optional, Type
from django.db import models
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
@ -49,7 +48,7 @@ class OAuthSource(Source):
consumer_secret = models.TextField()
@property
def type(self) -> "SourceType":
def type(self) -> Type["SourceType"]:
"""Return the provider instance for this source"""
from authentik.sources.oauth.types.manager import MANAGER
@ -67,6 +66,7 @@ class OAuthSource(Source):
@property
def ui_login_button(self) -> UILoginButton:
provider_type = self.type
return UILoginButton(
challenge=RedirectChallenge(
instance={
@ -77,7 +77,7 @@ class OAuthSource(Source):
),
}
),
icon_url=static(f"authentik/sources/{self.provider_type}.svg"),
icon_url=provider_type().icon_url(),
name=self.name,
)
@ -173,6 +173,16 @@ class OpenIDConnectOAuthSource(OAuthSource):
verbose_name_plural = _("OpenID OAuth Sources")
class AppleOAuthSource(OAuthSource):
"""Login using a apple.com."""
class Meta:
abstract = True
verbose_name = _("Apple OAuth Source")
verbose_name_plural = _("Apple OAuth Sources")
class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider."""

View File

@ -0,0 +1,102 @@
"""Apple OAuth Views"""
from base64 import b64decode
from json import loads
from time import time
from typing import Any, Optional
from jwt import encode
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.types.manager import MANAGER, SourceType
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger()
class AppleOAuthClient(OAuth2Client):
"""Apple OAuth2 client"""
def get_client_id(self) -> str:
parts = self.source.consumer_key.split(";")
if len(parts) < 3:
return self.source.consumer_key
return parts[0]
def get_client_secret(self) -> str:
now = time()
parts = self.source.consumer_key.split(";")
if len(parts) < 3:
raise ValueError(
(
"Apple Source client_id should be formatted like "
"services_id_identifier;apple_team_id;key_id"
)
)
LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2])
payload = {
"iss": parts[1],
"iat": now,
"exp": now + 86400 * 180,
"aud": "https://appleid.apple.com",
"sub": self.source.consumer_key,
}
# pyright: reportGeneralTypeIssues=false
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2]})
LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt)
return jwt
def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
id_token = token.get("id_token")
_, raw_payload, _ = id_token.split(".")
payload = loads(b64decode(raw_payload.encode().decode()))
return payload
class AppleOAuthRedirect(OAuthRedirect):
"""Apple OAuth2 Redirect"""
client_class = AppleOAuthClient
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "name email",
"response_mode": "form_post",
}
class AppleOAuth2Callback(OAuthCallback):
"""Apple OAuth2 Callback"""
client_class = AppleOAuthClient
def get_user_id(self, info: dict[str, Any]) -> Optional[str]:
return info["sub"]
def get_user_enroll_context(
self,
info: dict[str, Any],
) -> dict[str, Any]:
print(info)
return {
"email": info.get("email"),
"name": info.get("name"),
}
@MANAGER.type()
class AppleType(SourceType):
"""Apple Type definition"""
callback_view = AppleOAuth2Callback
redirect_view = AppleOAuthRedirect
name = "Apple"
slug = "apple"
authorization_url = "https://appleid.apple.com/auth/authorize"
access_token_url = "https://appleid.apple.com/auth/token" # nosec
profile_url = ""
def icon_url(self) -> str:
return "https://appleid.cdn-apple.com/appleid/button/logo"

View File

@ -1,7 +1,8 @@
"""Source type manager"""
from enum import Enum
from typing import Callable, Optional
from typing import Callable, Optional, Type
from django.templatetags.static import static
from structlog.stdlib import get_logger
from authentik.sources.oauth.views.callback import OAuthCallback
@ -22,8 +23,8 @@ class SourceType:
callback_view = OAuthCallback
redirect_view = OAuthRedirect
name: str
slug: str
name: str = "default"
slug: str = "default"
urls_customizable = False
@ -32,12 +33,16 @@ class SourceType:
access_token_url: Optional[str] = None
profile_url: Optional[str] = None
def icon_url(self) -> str:
"""Get Icon URL for login"""
return static(f"authentik/sources/{self.slug}.svg")
class SourceTypeManager:
"""Manager to hold all Source types."""
def __init__(self) -> None:
self.__sources: list[SourceType] = []
self.__sources: list[Type[SourceType]] = []
def type(self):
"""Class decorator to register classes inline."""
@ -56,14 +61,14 @@ class SourceTypeManager:
"""Get list of tuples of all registered names"""
return [(x.slug, x.name) for x in self.__sources]
def find_type(self, type_name: str) -> SourceType:
def find_type(self, type_name: str) -> Type[SourceType]:
"""Find type based on source"""
found_type = None
for src_type in self.__sources:
if src_type.slug == type_name:
return src_type
if not found_type:
found_type = SourceType()
found_type = SourceType
LOGGER.warning(
"no matching type found, using default",
wanted=type_name,

View File

@ -24,7 +24,7 @@ class OAuthCallback(OAuthClientMixin, View):
source: OAuthSource
# pylint: disable=too-many-return-statements
def get(self, request: HttpRequest, *_, **kwargs) -> HttpResponse:
def dispatch(self, request: HttpRequest, *_, **kwargs) -> HttpResponse:
"""View Get handler"""
slug = kwargs.get("source_slug", "")
try:

View File

@ -1,6 +1,8 @@
"""Dispatch OAuth views to respective views"""
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from authentik.sources.oauth.models import OAuthSource
@ -9,6 +11,7 @@ from authentik.sources.oauth.types.manager import MANAGER, RequestKind
LOGGER = get_logger()
@method_decorator(csrf_exempt, name="dispatch")
class DispatcherView(View):
"""Dispatch OAuth Redirect/Callback views to their proper class based on URL parameters"""

View File

@ -17,7 +17,7 @@ from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.flows.challenge import RedirectChallenge
from authentik.flows.views import to_stage_response
from authentik.flows.views.executor import to_stage_response
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth, PlexSourceFlowManager

View File

@ -0,0 +1,210 @@
# Generated by Django 3.2.8 on 2021-10-11 15:55
import django.db.models.deletion
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.lib.utils.time
from authentik.sources.saml.processors import constants
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SAMLSource = apps.get_model("authentik_sources_saml", "SAMLSource")
signature_translation_map = {
"rsa-sha1": constants.RSA_SHA1,
"rsa-sha256": constants.RSA_SHA256,
"ecdsa-sha256": constants.RSA_SHA256,
"dsa-sha1": constants.DSA_SHA1,
}
digest_translation_map = {
"sha1": constants.SHA1,
"sha256": constants.SHA256,
}
for source in SAMLSource.objects.all():
source.signature_algorithm = signature_translation_map.get(
source.signature_algorithm, constants.RSA_SHA256
)
source.digest_algorithm = digest_translation_map.get(
source.digest_algorithm, constants.SHA256
)
source.save()
class Migration(migrations.Migration):
replaces = [
("authentik_sources_saml", "0001_initial"),
("authentik_sources_saml", "0002_auto_20200523_2329"),
("authentik_sources_saml", "0003_auto_20200624_1957"),
("authentik_sources_saml", "0004_auto_20200708_1207"),
("authentik_sources_saml", "0005_samlsource_name_id_policy"),
("authentik_sources_saml", "0006_samlsource_allow_idp_initiated"),
("authentik_sources_saml", "0007_auto_20201112_1055"),
("authentik_sources_saml", "0008_auto_20201112_2016"),
("authentik_sources_saml", "0009_auto_20210301_0949"),
]
initial = True
dependencies = [
("authentik_core", "0001_initial"),
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_crypto", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="SAMLSource",
fields=[
(
"source_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_core.source",
),
),
(
"issuer",
models.TextField(
blank=True,
default=None,
help_text="Also known as Entity ID. Defaults the Metadata URL.",
verbose_name="Issuer",
),
),
(
"sso_url",
models.URLField(
help_text="URL that the initial Login request is sent to.",
verbose_name="SSO URL",
),
),
(
"slo_url",
models.URLField(
blank=True,
default=None,
help_text="Optional URL if your IDP supports Single-Logout.",
null=True,
verbose_name="SLO URL",
),
),
(
"signing_kp",
models.ForeignKey(
blank=True,
default=None,
help_text="Keypair which is used to sign outgoing requests. Leave empty to disable signing.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_crypto.certificatekeypair",
verbose_name="Singing Keypair",
),
),
(
"binding_type",
models.CharField(
choices=[
("REDIRECT", "Redirect Binding"),
("POST", "POST Binding"),
("POST_AUTO", "POST Binding with auto-confirmation"),
],
default="REDIRECT",
max_length=100,
),
),
(
"temporary_user_delete_after",
models.TextField(
default="days=1",
help_text="Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
verbose_name="Delete temporary users after",
),
),
(
"name_id_policy",
models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
(
"allow_idp_initiated",
models.BooleanField(
default=False,
help_text="Allows authentication flows initiated by the IdP. This can be a security risk, as no validation of the request ID is done.",
),
),
(
"digest_algorithm",
models.CharField(
choices=[
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
],
default="http://www.w3.org/2001/04/xmlenc#sha256",
max_length=50,
),
),
(
"signature_algorithm",
models.CharField(
choices=[
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
],
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
max_length=50,
),
),
],
options={
"verbose_name": "SAML Source",
"verbose_name_plural": "SAML Sources",
},
bases=("authentik_core.source",),
),
migrations.RunPython(
code=update_algorithms,
),
migrations.AlterField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View File

@ -18,7 +18,7 @@ from authentik.flows.planner import (
PLAN_CONTEXT_SSO,
FlowPlanner,
)
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.policies.utils import delete_none_keys
from authentik.sources.saml.exceptions import (

View File

@ -3,7 +3,12 @@ from django.utils.timezone import now
from structlog.stdlib import get_logger
from authentik.core.models import AuthenticatedSession, User
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.events.monitored_tasks import (
MonitoredTask,
TaskResult,
TaskResultStatus,
prefill_task,
)
from authentik.lib.utils.time import timedelta_from_string
from authentik.root.celery import CELERY_APP
from authentik.sources.saml.models import SAMLSource
@ -12,6 +17,7 @@ LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task()
def clean_temporary_users(self: MonitoredTask):
"""Remove temporary users created by SAML Sources"""
_now = now()

View File

@ -22,7 +22,7 @@ from authentik.flows.planner import (
FlowPlanner,
)
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.lib.utils.urls import redirect_with_qs
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64

View File

@ -12,7 +12,7 @@ from authentik.flows.challenge import (
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views import InvalidStageError
from authentik.flows.views.executor import InvalidStageError
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
LOGGER = get_logger()

View File

@ -0,0 +1,81 @@
"""AuthenticatorSMSStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice
class AuthenticatorSMSStageSerializer(StageSerializer):
"""AuthenticatorSMSStage Serializer"""
class Meta:
model = AuthenticatorSMSStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"provider",
"from_number",
"account_sid",
"auth",
"auth_password",
"auth_type",
]
class AuthenticatorSMSStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorSMSStage Viewset"""
queryset = AuthenticatorSMSStage.objects.all()
serializer_class = AuthenticatorSMSStageSerializer
filterset_fields = "__all__"
ordering = ["name"]
class SMSDeviceSerializer(ModelSerializer):
"""Serializer for sms authenticator devices"""
class Meta:
model = SMSDevice
fields = ["name", "pk", "phone_number"]
depth = 2
extra_kwargs = {
"phone_number": {"read_only": True},
}
class SMSDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Viewset for sms authenticator devices"""
queryset = SMSDevice.objects.all()
serializer_class = SMSDeviceSerializer
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
class SMSAdminDeviceViewSet(ReadOnlyModelViewSet):
"""Viewset for sms authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = SMSDevice.objects.all()
serializer_class = SMSDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View File

@ -0,0 +1,10 @@
"""SMS"""
from django.apps import AppConfig
class AuthentikStageAuthenticatorSMSConfig(AppConfig):
"""SMS App config"""
name = "authentik.stages.authenticator_sms"
label = "authentik_stages_authenticator_sms"
verbose_name = "authentik Stages.Authenticator.SMS"

View File

@ -0,0 +1,100 @@
# Generated by Django 3.2.8 on 2021-10-09 19:15
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_flows", "0024_alter_flow_compatibility_mode"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AuthenticatorSMSStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
("provider", models.TextField(choices=[("twilio", "Twilio")])),
("twilio_account_sid", models.TextField()),
("twilio_auth", models.TextField()),
(
"configure_flow",
models.ForeignKey(
blank=True,
help_text="Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_flows.flow",
),
),
],
options={
"verbose_name": "SMS Authenticator Setup Stage",
"verbose_name_plural": "SMS Authenticator Setup Stages",
},
bases=("authentik_flows.stage", models.Model),
),
migrations.CreateModel(
name="SMSDevice",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"name",
models.CharField(
help_text="The human-readable name of this device.", max_length=64
),
),
(
"confirmed",
models.BooleanField(default=True, help_text="Is this device ready for use?"),
),
("token", models.CharField(blank=True, max_length=16, null=True)),
(
"valid_until",
models.DateTimeField(
default=django.utils.timezone.now,
help_text="The timestamp of the moment of expiry of the saved token.",
),
),
("phone_number", models.TextField()),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_sms.authenticatorsmsstage",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "SMS Device",
"verbose_name_plural": "SMS Devices",
},
),
]

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