Compare commits

..

200 Commits

Author SHA1 Message Date
2b730dec54 release: 2021.9.6 2021-10-05 22:22:54 +02:00
2aacb311bc internal: add internal healthchecking to prevent websocket errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 22:22:38 +02:00
40055ef01b cmd: prevent outposts from panicking when failing to get their config
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 22:22:38 +02:00
75608dce5c web: add locale detection
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/interfaces/locale.ts
2021-10-05 21:22:08 +02:00
b0f7083879 lifecycle: fix syntax error in ak wrapper
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 21:04:01 +02:00
62bf79ce32 root: add docker-native healthcheck for web and celery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:45:38 +02:00
7a16c9cb14 root: remove redundant internal network from compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:45:38 +02:00
d29d161ac6 admin: clear update notification when notification's version matches current version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 20:45:38 +02:00
653631ac77 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>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
# Conflicts:
#	web/src/locales/fr_FR.po
2021-10-05 16:24:48 +02:00
cde303e780 web: fix strings not being translated at all when matching browser locale not found
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/interfaces/locale.ts
2021-10-05 16:24:31 +02:00
aa359a032c 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:49:15 +02:00
6491065aab 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:49:15 +02:00
79eec5a3a0 core: include group uuids in self serializer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:49:14 +02:00
cd5e091937 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>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
# Conflicts:
#	go.mod
#	go.sum
2021-10-05 13:49:11 +02:00
7ed8952803 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 13:48:53 +02:00
c1f302fb7c core: only return group names for user_self
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:48:53 +02:00
cb31e52d0e web/flows: adjust message for email stage
closes #1538

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

# Conflicts:
#	web/src/locales/fr_FR.po
2021-10-05 13:48:51 +02:00
782764ac73 api: ensure viewsets have default ordering
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:48:42 +02:00
d0c56325ef web/elements: fix model form always loading when viewport check is disabled
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:48:42 +02:00
73d57d6f82 core: make user's name field fully options
closes #1537

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:48:42 +02:00
2716a26887 web: Update Web API Client version (#1539)
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>
# Conflicts:
#	web/package-lock.json
#	web/package.json
2021-10-05 13:48:23 +02:00
0452537e8b web/admin: only show outpost deployment info when not embedded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:47:55 +02:00
d1a1bfbbc5 web/user: don't show managed tokens in user interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-05 13:47:49 +02:00
a69fcbca9a web: fix rendering of token copy button in dark mode
closes #1528

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

# Conflicts:
#	web/src/locales/fr_FR.po
2021-10-05 13:47:29 +02:00
1ac4dacc3b 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 13:47:15 +02:00
bcf7e162a4 release: 2021.9.5 2021-10-04 20:08:46 +02:00
f44956bd61 web: Update Web API Client version (#1526)
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:52:27 +02:00
cb37e5c10e stages/email: add activate_user_on_success flag, add for all example flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/locales/fr_FR.po
2021-10-04 18:50:19 +02:00
73bb778d62 stages/user_login: add check for user.is_active and tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:50:00 +02:00
b612a82e16 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:49:57 +02:00
83991c743e lifecycle: switch to h11 uvicorn worker for now
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:49:54 +02:00
09f43ca43b events: add missing migration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:49:50 +02:00
1c91835a26 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 18:49:45 +02:00
c032914092 web/admin: fix search group label
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:49:42 +02:00
3634bf4629 tests/integration: fix tests failing due to incorrect comparison
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-04 18:49:10 +02:00
45f99fbaf0 outposts: fix circular import in kubernetes controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:25:26 +02:00
e31a3307b5 providers/proxy: always check ingress secret in kubernetes controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:14:42 +02:00
d28fcca344 outposts: check ports of deployment in kubernetes outpost controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:14:42 +02:00
c296e1214c web: fix package lock
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 19:14:37 +02:00
d676cf6e3f outposts/proxy: show full error message when user is authenticated
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 18:20:44 +02:00
39d87841d0 outposts/proxy: add new headers with unified naming
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 18:20:44 +02:00
fcd879034c outpost/proxy: fix missing negation for internal host ssl verification
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 18:20:44 +02:00
b285814e24 sources/ldap: fix logic error in Active Directory account disabled status
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-10-03 18:19:07 +02:00
1a6ea72c09 release: 2021.9.4 2021-10-01 09:51:51 +02:00
c251b87f8c sources/ldap: add support for Active Directory userAccountControl attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:34:43 +02:00
21a9aa229a 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 19:34:43 +02:00
5f6565ee27 web/admin: fix LDAP Source form not exposing syncParentGroup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 19:34:43 +02:00
afad55a357 build(deps): bump sentry-sdk from 1.4.2 to 1.4.3 (#1502) 2021-09-30 19:34:43 +02:00
f25d76fa43 build(deps): bump sentry-sdk from 1.4.1 to 1.4.2 (#1488) 2021-09-30 19:34:42 +02:00
10b45d954e outposts: allow disabling of docker controller port mapping
closes #1474

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 00:11:50 +02:00
339eaf37f2 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-30 00:11:50 +02:00
f723fdd551 web/elements: fix initialLoad not being done when viewportCheck was disabled
closes #1497

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-30 00:11:50 +02:00
8359f0bfb3 web: fix linting again
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:20:40 +02:00
ee610a906a web: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:07:04 +02:00
828eeb5ebb root: Use fully qualified names for docker bases base images. (#1490)
Signed-off-by: Steven Armstrong <steven@armstrong.cc>
2021-09-29 10:04:27 +02:00
c9c177d8f9 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 10:02:06 +02:00
c19afa4f16 outposts/proxy: fix duplicate protocol in domain auth mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-29 10:02:01 +02:00
941bc61b31 release: 2021.9.3 2021-09-27 17:31:50 +02:00
282b364606 stages/prompt: fix inconsistent policy context for validation policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 17:05:26 +02:00
ad4bc4083d website/docs: update dev docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 16:04:41 +02:00
ebe282eb1a web/admin: fix user_write form not writing group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 10:12:45 +02:00
830c26ca25 tests/e2e: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-27 09:52:47 +02:00
ed3b4a3d4a build(deps): bump rapidoc from 9.1.2 to 9.1.3 in /website (#1478)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.2 to 9.1.3.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.2...v9.1.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:24:43 +02:00
975c4ddc04 build(deps): bump postcss from 8.3.7 to 8.3.8 in /website (#1479)
Bumps [postcss](https://github.com/postcss/postcss) from 8.3.7 to 8.3.8.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.3.7...8.3.8)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:49 +02:00
7e2896298a build(deps): bump rapidoc from 9.1.2 to 9.1.3 in /web (#1480)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.2 to 9.1.3.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.2...v9.1.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:31 +02:00
cba9cf8361 build(deps): bump actions/github-script from 4.1 to 5 (#1481)
Bumps [actions/github-script](https://github.com/actions/github-script) from 4.1 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4.1...v5)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:21 +02:00
bf12580f64 build(deps): bump pycryptodome from 3.10.3 to 3.10.4 (#1482)
Bumps [pycryptodome](https://github.com/Legrandin/pycryptodome) from 3.10.3 to 3.10.4.
- [Release notes](https://github.com/Legrandin/pycryptodome/releases)
- [Changelog](https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst)
- [Commits](https://github.com/Legrandin/pycryptodome/commits/v3.10.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:22:03 +02:00
75ef4ce596 tests/e2e: add new ldap object classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:57:42 +02:00
c2f3ce11b0 outposts/ldap: fix potential panic when converting attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:52:25 +02:00
3c256fecc6 outposts/ldap: add groupofuniquenames
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:49:11 +02:00
0285b84133 outposts/ldap: add query support for all supported object classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:42:26 +02:00
99a371a02c web/elements: fix token copy button not working on chrome...
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:34:28 +02:00
c7e6eb8896 outposts/ldap: add support for base scope and domain info
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 14:01:22 +02:00
674bd9e05c web/admin: Fix typo 'username address' -> 'username' (#1473) 2021-09-26 12:53:37 +02:00
b79901df87 website/docs: prepare 2021.9.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 12:03:10 +02:00
b248f450dd outposts: make AUTHENTIK_HOST_BROWSER configurable from central config
closes #1471

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 12:00:51 +02:00
05db9e5c40 web/admin: handle error correctly when creating user recovery link
closes #1472

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 11:49:40 +02:00
234a5e2b66 outposts: fix outposts not correctly updating central state
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-26 11:40:21 +02:00
aea1736f70 outposts/proxy: Fix failing traefik healtcheck (#1470) 2021-09-26 11:33:18 +02:00
9f4a4449f5 outposts/proxy: ensure cookies only last as long as tokens
closes #1462

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 16:12:59 +02:00
b6b55e2336 build(deps): bump goauthentik.io/api from 0.202192.3 to 0.202192.5 (#1468)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202192.3 to 0.202192.5.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202192.3...v0.202192.5)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-25 16:06:10 +02:00
8f2805e05b web: Update Web API Client version (#1467)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 16:04:07 +02:00
4f3583cd7e providers/proxy: make token_validity float and optional for backwards compat
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 15:54:32 +02:00
617e90dca3 web: Update Web API Client version (#1465)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 15:48:05 +02:00
f7408626a8 providers/proxy: return token_validity as total seconds instead of expression
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 15:44:16 +02:00
4dcb15af46 build(deps): bump goauthentik.io/api from 0.202192.1 to 0.202192.3 (#1464)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202192.1 to 0.202192.3.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202192.1...v0.202192.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-25 15:11:08 +02:00
89beb7a9f7 web: Update Web API Client version (#1463)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-25 15:02:33 +02:00
28eeb4798e providers/proxy: add token_validity field for outpost configuration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1462
2021-09-25 15:00:06 +02:00
79b92e764e *: fix typos in code
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-25 00:01:11 +02:00
919336a519 outposts: ensure service is always re-created with mismatching ports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 23:45:15 +02:00
27e04589c1 outposts/proxyv2: fix routing not working correctly for domain auth
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 23:32:16 +02:00
ba44fbdac8 website/docs: fix typos and grammar (#1459) 2021-09-24 15:37:54 +02:00
0e093a8917 web: Update Web API Client version (#1458) 2021-09-24 12:23:14 +02:00
d0bfb99859 web/elements: improve error handling on forms
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 12:19:56 +02:00
93bdea3769 core: fix api return code for user self-update
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:51:03 +02:00
e681654af7 web/admin: add notice for recovery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:50:52 +02:00
cab7593dca web/user: fix brand not being shown in safari
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 11:50:46 +02:00
cf92f9aefc web/elements: fix token copy error in safari
closes #1219

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 10:44:28 +02:00
8d72b3498d internal: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-24 10:44:28 +02:00
42ab858c50 build(deps): bump goauthentik.io/api from 0.202191.4 to 0.202192.1 (#1455)
Bumps [goauthentik.io/api](https://github.com/goauthentik/client-go) from 0.202191.4 to 0.202192.1.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v0.202191.4...v0.202192.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-24 09:34:10 +02:00
a1abae9ab1 build(deps): bump boto3 from 1.18.46 to 1.18.47 (#1456)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.46 to 1.18.47.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.46...1.18.47)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-24 09:34:01 +02:00
8f36b49061 web/user: search apps when user typed before apps have loaded
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:34:11 +02:00
64b4e851ce events: add additional validation for event transport
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:29:58 +02:00
40a62ac1e5 web/admin: fix Transport Form not loading mode correctly on edit
closes #1453

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:16:38 +02:00
5df60e4d87 web/admin: fix NotificationWebhookMapping not loading correctly
closes #1452

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 16:13:58 +02:00
50ebc8522d web: Update Web API Client version (#1454)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-23 14:21:49 +02:00
eddca478dc release: 2021.9.2 2021-09-23 12:34:02 +02:00
99a7fca08e website/docs: prepare 2021.9.2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 12:33:42 +02:00
a7e3602908 web: fix import order of polyfills causing shadydom to not work on firefox and safari
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 10:16:49 +02:00
74169860cf api: add logging to sentry proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 09:57:42 +02:00
52bb774f73 internal: add asset paths for user interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-23 09:57:24 +02:00
f26fcaf825 website/docs: add warning for example flows (#1444) 2021-09-23 08:34:40 +02:00
b8e92e2f11 build(deps): bump postcss from 8.3.6 to 8.3.7 in /website (#1445) 2021-09-23 08:33:27 +02:00
08adfc94d6 build(deps): bump rollup from 2.56.3 to 2.57.0 in /web (#1446) 2021-09-23 08:33:18 +02:00
236fafb735 build(deps): bump boto3 from 1.18.45 to 1.18.46 (#1447) 2021-09-23 08:33:10 +02:00
5ad9ddee3c build(deps): bump goauthentik.io/api from 0.202191.3 to 0.202191.4 (#1449) 2021-09-23 08:33:01 +02:00
24d220ff49 build(deps): bump urllib3 from 1.26.6 to 1.26.7 (#1448) 2021-09-23 08:32:53 +02:00
3364c195b7 build(deps): bump sentry-sdk from 1.4.0 to 1.4.1 (#1450) 2021-09-23 08:32:43 +02:00
50aa87d141 web/user: enable sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 22:35:52 +02:00
72b375023d web: Update Web API Client version (#1443)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-22 20:53:25 +02:00
77ba186818 website/docs: add notice for guacamole token length
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 20:02:30 +02:00
2fe6de0505 release: 2021.9.1 2021-09-22 19:11:20 +02:00
bf9e969b53 website/docs: prepare 2021.9.1
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 18:58:52 +02:00
184f119b16 website: set use_global_settings to true for example flows with email stages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 18:52:55 +02:00
ebc06f1abe outposts/ldap: fix logic error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 13:19:50 +02:00
0f8880ab0a outposts: fix typo
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-22 13:14:28 +02:00
ee56da5092 build(deps): bump @sentry/tracing from 6.13.1 to 6.13.2 in /web (#1438) 2021-09-22 07:32:40 +02:00
2152004502 build(deps): bump @types/codemirror from 5.60.2 to 5.60.3 in /web (#1437) 2021-09-22 07:30:35 +02:00
45d0b80d02 build(deps): bump @sentry/browser from 6.13.1 to 6.13.2 in /web (#1439) 2021-09-22 07:30:27 +02:00
96065eb942 build(deps): bump boto3 from 1.18.44 to 1.18.45 (#1441) 2021-09-22 07:30:01 +02:00
ac944fee8b build(deps): bump drf-spectacular from 0.18.2 to 0.19.0 (#1442) 2021-09-22 07:29:52 +02:00
1d0e5fc353 build(deps): bump sentry-sdk from 1.3.1 to 1.4.0 (#1440) 2021-09-22 07:28:48 +02:00
1f97420207 outposts/ldap: allow custom attributes to shadow built-in attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 21:59:39 +02:00
ae07f13a87 outposts: don't map port 9300 on docker, only expose port
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 21:40:08 +02:00
0aec504170 website/docs: add ssl port for ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 15:44:05 +02:00
3b4c9bcc57 root: use tagged go client version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 15:42:07 +02:00
5182a6741e root: format pyproject
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 13:32:28 +02:00
da7635ae5c web: sort imports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 11:33:51 +02:00
a92a0fb60a web: migrate to lit 2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-21 11:19:26 +02:00
cb10c1753b build(deps): bump lit-html from 1.4.1 to 2.0.0 in /web (#1427) 2021-09-21 08:35:36 +02:00
ae654bd4c8 build(deps): bump lit-element from 2.5.1 to 3.0.0 in /web (#1433) 2021-09-21 08:32:15 +02:00
28192655ec build(deps): bump @typescript-eslint/eslint-plugin in /web (#1426) 2021-09-21 08:32:00 +02:00
9582294eb8 build(deps): bump @sentry/tracing from 6.12.0 to 6.13.1 in /web (#1428) 2021-09-21 08:31:48 +02:00
0172430d7d build(deps): bump @patternfly/patternfly from 4.132.2 to 4.135.2 in /web (#1429) 2021-09-21 08:30:44 +02:00
1454b65933 build(deps): bump @typescript-eslint/parser in /web (#1430) 2021-09-21 08:30:36 +02:00
432a7792e2 build(deps): bump @sentry/browser from 6.12.0 to 6.13.1 in /web (#1431) 2021-09-21 08:30:28 +02:00
54069618b4 build(deps): bump codemirror from 5.62.3 to 5.63.0 in /web (#1432) 2021-09-21 08:30:20 +02:00
81feb313df build(deps): bump geoip2 from 4.2.0 to 4.3.0 (#1434) 2021-09-21 08:29:33 +02:00
e6b275add3 stages/invitation: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 20:41:05 +02:00
27016a5527 stages/invitation: fix tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 20:30:51 +02:00
4c29d517f0 stages/email: use different query arguments for email and invitation tokens
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:55:53 +02:00
180d27cc37 outposts: don't restart container when health checks are starting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:46:05 +02:00
5a8b356dc7 web: fix css for dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:38:25 +02:00
3195640776 stages/email: slugify token identifier
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:26:25 +02:00
f463296d47 web: Update Web API Client version (#1421)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-20 19:22:46 +02:00
adf4b23c01 website/docs: add /akprox for nginx auth_request (#1420) 2021-09-20 19:21:30 +02:00
d900a2b6a9 *: fix lookup_fields
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:19:36 +02:00
95a2fddfa8 policies/expression: add ak_user_has_authenticator
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:13:41 +02:00
8f7d21b692 stages/email: don't throw 404 when token can't be found
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 19:01:25 +02:00
3f84abec2f core: fix token identifier not being slugified when created with user-controller input
closes #1390

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 13:43:25 +02:00
b5c857aff4 api: add explicit lookup_value_regex, disable include_format_suffixes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 13:42:56 +02:00
f8dee09107 web/user: allow customisable background colour
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 12:49:17 +02:00
84a800583c web/user: make search configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-20 12:15:53 +02:00
88de94f014 build(deps): bump rapidoc from 9.1.0 to 9.1.2 in /website (#1418)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.0 to 9.1.2.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.0...v9.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 11:50:38 +02:00
25549ec339 build(deps): bump rapidoc from 9.1.0 to 9.1.2 in /web (#1419)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.1.0 to 9.1.2.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/v9.1.0...v9.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 11:50:27 +02:00
fe4923bff6 build(deps): bump boto3 from 1.18.43 to 1.18.44 (#1417)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.43 to 1.18.44.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.43...1.18.44)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-20 09:09:22 +02:00
bb1a0b6bd2 web: Update Web API Client version (#1416)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-19 22:32:38 +02:00
879b5ead71 web: fix notification badge not refreshing after clearing notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:58:59 +02:00
1670ec9167 website/docs: update 2021.9.1-rc3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:55:21 +02:00
ac52667327 release: 2021.9.1-rc3 2021-09-19 21:52:49 +02:00
0d7c5c2108 web/user: fix edit button for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:10:57 +02:00
73e3d19384 web/user: add auto-focus search for applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 21:07:18 +02:00
f6e0f0282d core: fix tokens not being viewable but superusers
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 16:11:20 +02:00
3f42067a8f web: improve display of action buttons with non-primary classes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:57:12 +02:00
ed6f5b98df sources/ldap: improve messages of sync tasks in UI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:54:22 +02:00
dd290e264c web/admin: trigger refresh after syncing ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:34:44 +02:00
c85484fc00 core: allow admins to create tokens with all parameters, re-add user to token form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 15:25:48 +02:00
663dffd8be web/admin: fix error in firefox when creating token
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:28:40 +02:00
c15d0c3d17 web: fix text colour in delete form in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-19 00:14:50 +02:00
bf09a54f35 web/user: optimise load, fix unread status for notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 18:16:31 +02:00
930dd51663 web: fix datetime-local fields throwing errors on firefox
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 15:31:48 +02:00
12a523c7aa website: remove sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-18 14:33:53 +02:00
ea9a6d57dd build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /website (#1414)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:54 +02:00
91958e1232 build(deps): bump rapidoc from 9.0.0 to 9.1.0 in /web (#1415)
Bumps [rapidoc](https://github.com/mrin9/RapiDoc) from 9.0.0 to 9.1.0.
- [Release notes](https://github.com/mrin9/RapiDoc/releases)
- [Commits](https://github.com/mrin9/RapiDoc/compare/9.0.0...v9.1.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-18 11:23:44 +02:00
8925afb089 web/admin: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:54:48 +02:00
ccafe7be4f website/docs: update 2021.9 changelog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:36 +02:00
8279690a8f sources/ldap: prevent error when retrying old system task with no arguments
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:49:26 +02:00
763d3ae76a web/admin: fix ldap sync status for new API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 16:46:18 +02:00
b775e7f4d3 web: Update Web API Client version (#1412)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-17 12:45:16 +02:00
3d8d93ece5 root: log failed celery tasks to event log
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:42 +02:00
06af306e8a sources/ldap: bump timeout, run each sync component in its own task
closes #1411

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 12:42:20 +02:00
9257f3c919 web/user: add missing stop impersonation button
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 10:04:47 +02:00
2fe7f4cf04 web/user: fix final redirect after stage setup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:14 +02:00
04399bc8bb web/admin: fix settings link on user avatar
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-09-17 09:53:04 +02:00
fcbcfbc3c0 website/docs: Minor LDAP and NGINX Documentation Updates (#1406)
* update LDAP documentation

* include domain level nginx forward auth example

* wrap in banner

* update placeholder
2021-09-17 09:47:27 +02:00
3e4ce62dfe build(deps-dev): bump pylint from 2.10.2 to 2.11.1 (#1409)
* build(deps-dev): bump pylint from 2.10.2 to 2.11.1

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

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

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

* root: update pylint config

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-09-17 09:46:39 +02:00
d8292151e6 build(deps-dev): bump prettier from 2.4.0 to 2.4.1 in /website (#1407) 2021-09-17 09:17:20 +02:00
3d01a59b34 build(deps): bump prettier from 2.4.0 to 2.4.1 in /web (#1408) 2021-09-17 09:17:12 +02:00
5df15c4105 build(deps): bump boto3 from 1.18.42 to 1.18.43 (#1410) 2021-09-17 09:16:43 +02:00
75d695105d web: Update Web API Client version (#1405)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-09-16 23:44:53 +02:00
397 changed files with 11526 additions and 3702 deletions

View File

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

1
.github/codespell-words.txt vendored Normal file
View File

@ -0,0 +1 @@
keypair

View File

@ -61,7 +61,7 @@ jobs:
npm install
- name: Generate API
run: make gen-web
- name: prettier
- name: lit-analyse
run: |
cd web
npm run lit-analyse

View File

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

View File

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

View File

@ -117,7 +117,7 @@ This section guides you through submitting a bug report for authentik. Following
Whenever authentik encounters an error, it will be logged as an Event with the type `system_exception`. This event type has a button to directly open a pre-filled GitHub issue form.
This form will have the full stack trace of the error that ocurred and shouldn't contain any sensitive data.
This form will have the full stack trace of the error that occurred and shouldn't contain any sensitive data.
### Suggesting Enhancements

View File

@ -1,5 +1,5 @@
# Stage 1: Lock python dependencies
FROM python:3.9-slim-buster as locker
FROM docker.io/python:3.9-slim-buster as locker
COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/
@ -11,7 +11,7 @@ RUN pip install pipenv && \
pipenv lock -r --dev-only > requirements-dev.txt
# Stage 2: Build website
FROM node as website-builder
FROM docker.io/node as website-builder
COPY ./website /static/
@ -19,7 +19,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-docs-only
# Stage 3: Build webui
FROM node as web-builder
FROM docker.io/node as web-builder
COPY ./web /static/
@ -27,7 +27,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
# Stage 4: Build go proxy
FROM golang:1.17.1 AS builder
FROM docker.io/golang:1.17.1 AS builder
WORKDIR /work
@ -47,7 +47,7 @@ COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
FROM python:3.9-slim-buster
FROM docker.io/python:3.9-slim-buster
WORKDIR /
COPY --from=locker /app/requirements.txt /
@ -80,8 +80,12 @@ COPY ./lifecycle/ /lifecycle
COPY --from=builder /work/authentik /authentik-proxy
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" ]
ENTRYPOINT [ "/lifecycle/ak" ]

View File

@ -20,6 +20,7 @@ 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
lint:
pyright authentik tests lifecycle

View File

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

323
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "19d5324fd1a4af125ed57a683030ca14ee2d3648117748e4b32656875484728e"
"sha256": "babb6061c555f8f239f00210b2a0356763bdaaca2f3d704cf3444891b84db84d"
},
"pipfile-spec": 6,
"requires": {},
@ -120,27 +120,27 @@
},
"boto3": {
"hashes": [
"sha256:63b9846c26e0905f4e9e39d6b59f152330c53a926d693439161c43dcf9779365",
"sha256:a9232185d8e7e2fd2b166c0ebee5d7b1f787fdb3093f33bbf5aa932c08f0ccac"
"sha256:7b45b224442c479de4bc6e6e9cb0557b642fc7a77edc8702e393ccaa2e0aa128",
"sha256:c388da7dc1a596755f39de990a72e05cee558d098e81de63de55bd9598cc5134"
],
"index": "pypi",
"version": "==1.18.42"
"version": "==1.18.48"
},
"botocore": {
"hashes": [
"sha256:0952d1200968365b440045efe8e45bbae38cf603fee12bcfc3d7b5f963cbfa18",
"sha256:6de4fec4ee10987e4dea96f289553c2f45109fcaafcb74a5baee1221926e1306"
"sha256:17a10dd33334e7e3aaa4e12f66317284f96bb53267e20bc877a187c442681772",
"sha256:2089f9fa36a59d8c02435c49d58ccc7b3ceb9c0c054ea4f71631c3c3a1c5245e"
],
"markers": "python_version >= '3.6'",
"version": "==1.21.42"
"version": "==1.21.51"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
"sha256:0a3d3556c2c3befdbba2f93b78792c199c66201c999e97947ea0b7437758246b",
"sha256:6a6fa6802188ab7e77bab2db001d676e854499552b0037d999d5b9f211db5250"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
"version": "==4.2.3"
},
"cbor2": {
"hashes": [
@ -252,11 +252,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd"
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
],
"markers": "python_version >= '3'",
"version": "==2.0.5"
"version": "==2.0.6"
},
"click": {
"hashes": [
@ -268,9 +268,11 @@
},
"click-didyoumean": {
"hashes": [
"sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
],
"version": "==0.0.3"
"markers": "python_version < '4' and python_full_version >= '3.6.2'",
"version": "==0.3.0"
},
"click-plugins": {
"hashes": [
@ -286,6 +288,14 @@
],
"version": "==0.2.0"
},
"codespell": {
"hashes": [
"sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5",
"sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"
],
"index": "pypi",
"version": "==2.1.0"
},
"colorama": {
"hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
@ -303,25 +313,28 @@
},
"cryptography": {
"hashes": [
"sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e",
"sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b",
"sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
"sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
"sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
"sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
"sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
"sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
"sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
"sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
"sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb",
"sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14",
"sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af",
"sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e",
"sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5",
"sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06",
"sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"
"sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6",
"sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6",
"sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c",
"sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999",
"sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e",
"sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992",
"sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d",
"sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588",
"sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa",
"sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d",
"sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd",
"sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d",
"sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953",
"sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2",
"sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8",
"sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6",
"sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9",
"sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6",
"sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad",
"sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"
],
"version": "==3.4.8"
"version": "==35.0.0"
},
"dacite": {
"hashes": [
@ -369,11 +382,11 @@
},
"django-filter": {
"hashes": [
"sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06",
"sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"
"sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e",
"sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"
],
"index": "pypi",
"version": "==2.4.0"
"version": "==21.1"
},
"django-guardian": {
"hashes": [
@ -449,11 +462,11 @@
},
"drf-spectacular": {
"hashes": [
"sha256:47ef6ec8ff48ac8aede6ec12450a55fee381cf84de969ef1724dcde5a93de6b8",
"sha256:d746b936cb4cddec380ea95bf91de6a6721777dfc42e0eea53b83c61a625e94e"
"sha256:65df818226477cdfa629947ea52bc0cc13eb40550b192eeccec64a6b782651fd",
"sha256:f71205da3645d770545abeaf48e8a15afd6ee9a76e57c03df4592e51be1059bf"
],
"index": "pypi",
"version": "==0.18.2"
"version": "==0.19.0"
},
"duo-client": {
"hashes": [
@ -480,19 +493,19 @@
},
"geoip2": {
"hashes": [
"sha256:906a1dbf15a179a1af3522970e8420ab15bb3e0afc526942cc179e12146d9c1d",
"sha256:b97b44031fdc463e84eb1316b4f19edd978cb1d78703465fcb1e36dc5a822ba6"
"sha256:f150bed3190d543712a17467208388d31bd8ddb49b2226fba53db8aaedb8ba89",
"sha256:f9172cdfb2a5f9225ace5e30dd7426413ad28798a5f474cd1538780686bd6a87"
],
"index": "pypi",
"version": "==4.2.0"
"version": "==4.4.0"
},
"google-auth": {
"hashes": [
"sha256:7ae5eda089d393ca01658b550df24913cbbbdd34e9e6dedc1cea747485ae0c04",
"sha256:bde03220ed56e4e147dec92339c90ce95159dce657e2cccd0ac1fe82f6a96284"
"sha256:2a92b485afed5292946b324e91fcbe03db277ee4cb64c998c6cfa66d4af01dee",
"sha256:6dc8173abd50f25b6e62fc5b42802c96fc7cd9deb9bfeeb10a79f5606225cdf4"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.0"
"version": "==2.2.1"
},
"gunicorn": {
"hashes": [
@ -616,10 +629,10 @@
},
"jsonschema": {
"hashes": [
"sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
"sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
"sha256:bc51325b929171791c42ebc1c70b9713eb134d3bb8ebd5474c8b659b15be6d86",
"sha256:c773028c649441ab980015b5b622f4cd5134cf563daaf0235ca4b73cc3734f20"
],
"version": "==3.2.0"
"version": "==4.0.0"
},
"kombu": {
"hashes": [
@ -704,10 +717,10 @@
},
"maxminddb": {
"hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
"sha256:e37707ec4fab115804670e0fb7aedb4b57075a8b6f80052bdc648d3c005184e5"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.3"
"version": "==2.2.0"
},
"msgpack": {
"hashes": [
@ -898,39 +911,39 @@
},
"pycryptodome": {
"hashes": [
"sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0",
"sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d",
"sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce",
"sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06",
"sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35",
"sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27",
"sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129",
"sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9",
"sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673",
"sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1",
"sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6",
"sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8",
"sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c",
"sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713",
"sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6",
"sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438",
"sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e",
"sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07",
"sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6",
"sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd",
"sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6",
"sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8",
"sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427",
"sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067",
"sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8",
"sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b",
"sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa",
"sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf",
"sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da",
"sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"
"sha256:04e14c732c3693d2830839feed5129286ce47ffa8bfe90e4ae042c773e51c677",
"sha256:11d3164fb49fdee000fde05baecce103c0c698168ef1a18d9c7429dd66f0f5bb",
"sha256:217dcc0c92503f7dd4b3d3b7d974331a4419f97f555c99a845c3b366fed7056b",
"sha256:24c1b7705d19d8ae3e7255431efd2e526006855df62620118dd7b5374c6372f6",
"sha256:309529d2526f3fb47102aeef376b3459110a6af7efb162e860b32e3a17a46f06",
"sha256:3a153658d97258ca20bf18f7fe31c09cc7c558b6f8974a6ec74e19f6c634bd64",
"sha256:3f9fb499e267039262569d08658132c9cd8b136bf1d8c56b72f70ed05551e526",
"sha256:3faa6ebd35c61718f3f8862569c1f38450c24f3ededb213e1a64806f02f584bc",
"sha256:40083b0d7f277452c7f2dd4841801f058cc12a74c219ee4110d65774c6a58bef",
"sha256:49e54f2245befb0193848c8c8031d8d1358ed4af5a1ae8d0a3ba669a5cdd3a72",
"sha256:4e8fc4c48365ce8a542fe48bf1360da05bb2851df12f64fc94d751705e7cdbe7",
"sha256:54d4e4d45f349d8c4e2f31c2734637ff62a844af391b833f789da88e43a8f338",
"sha256:66301e4c42dee43ee2da256625d3fe81ef98cc9924c2bd535008cc3ad8ded77b",
"sha256:6b45fcace5a5d9c57ba87cf804b161adc62aa826295ce7f7acbcbdc0df74ed37",
"sha256:7efec2418e9746ec48e264eea431f8e422d931f71c57b1c96ee202b117f58fa9",
"sha256:851e6d4930b160417235955322db44adbdb19589918670d63f4acd5d92959ac0",
"sha256:8e82524e7c354033508891405574d12e612cc4fdd3b55d2c238fc1a3e300b606",
"sha256:8ec154ec445412df31acf0096e7f715e30e167c8f2318b8f5b1ab7c28f4c82f7",
"sha256:91ba4215a1f37d0f371fe43bc88c5ff49c274849f3868321c889313787de7672",
"sha256:97e7df67a4da2e3f60612bbfd6c3f243a63a15d8f4797dd275e1d7b44a65cb12",
"sha256:9a2312440057bf29b9582f72f14d79692044e63bfbc4b4bbea8559355f44f3dd",
"sha256:a7471646d8cd1a58bb696d667dcb3853e5c9b341b68dcf3c3cc0893d0f98ca5f",
"sha256:ac3012c36633564b2b5539bb7c6d9175f31d2ce74844e9abe654c428f02d0fd8",
"sha256:b1daf251395af7336ddde6a0015ba5e632c18fe646ba930ef87402537358e3b4",
"sha256:b217b4525e60e1af552d62bec01b4685095436d4de5ecde0f05d75b2f95ba6d4",
"sha256:c61ea053bd5d4c12a063d7e704fbe1c45abb5d2510dab55bd95d166ba661604f",
"sha256:c6469d1453f5864e3321a172b0aa671b938d753cbf2376b99fa2ab8841539bb8",
"sha256:cefe6b267b8e5c3c72e11adec35a9c7285b62e8ea141b63e87055e9a9e5f2f8c",
"sha256:d713dc0910e5ded07852a05e9b75f1dd9d3a31895eebee0668f612779b2a748c",
"sha256:db15fa07d2a4c00beeb5e9acdfdbc1c79f9ccfbdc1a8f36c82c4aa44951b33c9"
],
"index": "pypi",
"version": "==3.10.1"
"version": "==3.10.4"
},
"pyjwt": {
"hashes": [
@ -942,10 +955,10 @@
},
"pyopenssl": {
"hashes": [
"sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51",
"sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"
"sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3",
"sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"
],
"version": "==20.0.1"
"version": "==21.0.0"
},
"pyparsing": {
"hashes": [
@ -1082,11 +1095,11 @@
},
"sentry-sdk": {
"hashes": [
"sha256:ebe99144fa9618d4b0e7617e7929b75acd905d258c3c779edcd34c0adfffe26c",
"sha256:f33d34c886d0ba24c75ea8885a8b3a172358853c7cbde05979fc99c29ef7bc52"
"sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828",
"sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"
],
"index": "pypi",
"version": "==1.3.1"
"version": "==1.4.3"
},
"service-identity": {
"hashes": [
@ -1176,11 +1189,11 @@
"secure"
],
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"index": "pypi",
"version": "==1.26.6"
"version": "==1.26.7"
},
"uvicorn": {
"extras": [
@ -1410,11 +1423,11 @@
},
"astroid": {
"hashes": [
"sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c",
"sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"
"sha256:dcc06f6165f415220013801642bd6c9808a02967070919c4b746c6864c205471",
"sha256:fe81f80c0b35264acb5653302ffbd935d394f1775c5e4487df745bf9c2442708"
],
"markers": "python_version ~= '3.6'",
"version": "==2.7.3"
"version": "==2.8.0"
},
"attrs": {
"hashes": [
@ -1457,11 +1470,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:7098e7e862f6370a2a8d1a6398cd359815c45d12626267652c3f13dec58e2367",
"sha256:fa471a601dfea0f492e4f4fca035cd82155e65dc45c9b83bf4322dfab63755dd"
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
],
"markers": "python_version >= '3'",
"version": "==2.0.5"
"version": "==2.0.6"
},
"click": {
"hashes": [
@ -1547,11 +1560,11 @@
},
"gitpython": {
"hashes": [
"sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b",
"sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"
"sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647",
"sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"
],
"markers": "python_version >= '3.6'",
"version": "==3.1.18"
"markers": "python_version >= '3.7'",
"version": "==3.1.24"
},
"idna": {
"hashes": [
@ -1642,11 +1655,11 @@
},
"platformdirs": {
"hashes": [
"sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f",
"sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.0"
"version": "==2.4.0"
},
"pluggy": {
"hashes": [
@ -1666,11 +1679,11 @@
},
"pylint": {
"hashes": [
"sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1",
"sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"
"sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
"sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
],
"index": "pypi",
"version": "==2.10.2"
"version": "==2.11.1"
},
"pylint-django": {
"hashes": [
@ -1748,49 +1761,49 @@
},
"regex": {
"hashes": [
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
"sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656",
"sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a",
"sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae",
"sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0",
"sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1",
"sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7",
"sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3",
"sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c",
"sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7",
"sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3",
"sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673",
"sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c",
"sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e",
"sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d",
"sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2",
"sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72",
"sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3",
"sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11",
"sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90",
"sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5",
"sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec",
"sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a",
"sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561",
"sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab",
"sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078",
"sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc",
"sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1",
"sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c",
"sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6",
"sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48",
"sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456",
"sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8",
"sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18",
"sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4",
"sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736",
"sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949",
"sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346",
"sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db",
"sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad",
"sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9",
"sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"
],
"version": "==2021.8.28"
"version": "==2021.9.24"
},
"requests": {
"hashes": [
@ -1848,16 +1861,24 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"typing-extensions": {
"hashes": [
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
],
"version": "==3.10.0.2"
},
"urllib3": {
"extras": [
"secure"
],
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"index": "pypi",
"version": "==1.26.6"
"version": "==1.26.7"
},
"wrapt": {
"hashes": [

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.9.1-rc2"
__version__ = "2021.9.6"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -84,7 +84,7 @@ class SystemSerializer(PassiveSerializer):
return now()
def get_embedded_outpost_host(self, request: Request) -> str:
"""Get the FQDN configured on the embeddded outpost"""
"""Get the FQDN configured on the embedded outpost"""
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts.exists():
return ""

View File

@ -8,3 +8,8 @@ class AuthentikAdminConfig(AppConfig):
name = "authentik.admin"
label = "authentik_admin"
verbose_name = "authentik Admin"
def ready(self):
from authentik.admin.tasks import clear_update_notifications
clear_update_notifications.delay()

View File

@ -10,7 +10,7 @@ from requests import RequestException
from structlog.stdlib import get_logger
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.events.models import Event, EventAction
from authentik.events.models import Event, EventAction, Notification
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_http_session
@ -35,6 +35,18 @@ def _set_prom_info():
)
@CELERY_APP.task()
def clear_update_notifications():
"""Clear update notifications on startup if the notification was for the version
we're running now."""
for notification in Notification.objects.filter(event__action=EventAction.UPDATE_AVAILABLE):
if "new_version" not in notification.event.context:
continue
notification_version = notification.event.context["new_version"]
if notification_version == __version__:
notification.delete()
@CELERY_APP.task(bind=True, base=MonitoredTask)
def update_latest_version(self: MonitoredTask):
"""Update latest version info"""

View File

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

View File

@ -33,3 +33,12 @@ class OwnerPermissions(BasePermission):
if owner != request.user:
return False
return True
class OwnerSuperuserPermissions(OwnerPermissions):
"""Similar to OwnerPermissions, except always allow access for superusers"""
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
if request.user.is_superuser:
return True
return super().has_object_permission(request, view, obj)

View File

@ -5,6 +5,9 @@ from typing import Callable, Optional
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
LOGGER = get_logger()
def permission_required(perm: Optional[str] = None, other_perms: Optional[list[str]] = None):
@ -18,10 +21,12 @@ def permission_required(perm: Optional[str] = None, other_perms: Optional[list[s
if perm:
obj = self.get_object()
if not request.user.has_perm(perm, obj):
LOGGER.debug("denying access for object", user=request.user, perm=perm, obj=obj)
return self.permission_denied(request)
if other_perms:
for other_perm in other_perms:
if not request.user.has_perm(other_perm):
LOGGER.debug("denying access for other", user=request.user, perm=perm)
return self.permission_denied(request)
return func(self, request, *args, **kwargs)

View File

@ -11,7 +11,7 @@ from drf_spectacular.types import OpenApiTypes
def build_standard_type(obj, **kwargs):
"""Build a basic type with optional add ons."""
"""Build a basic type with optional add owns."""
schema = build_basic_type(obj)
schema.update(kwargs)
return schema

View File

@ -63,7 +63,7 @@ class ConfigView(APIView):
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrive public configuration options"""
"""Retrieve public configuration options"""
config = ConfigSerializer(
{
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"),

View File

@ -10,10 +10,13 @@ from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from structlog.stdlib import get_logger
from authentik.api.tasks import sentry_proxy
from authentik.lib.config import CONFIG
LOGGER = get_logger()
class PlainTextParser(BaseParser):
"""Plain text parser."""
@ -45,6 +48,7 @@ class SentryTunnelView(APIView):
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled
if not CONFIG.y_bool("error_reporting.enabled", False):
LOGGER.debug("error reporting disabled")
return HttpResponse(status=400)
# Body is 2 json objects separated by \n
full_body = request.body
@ -55,6 +59,7 @@ class SentryTunnelView(APIView):
# Check that the DSN is what we expect
dsn = header.get("dsn", "")
if dsn != settings.SENTRY_DSN:
LOGGER.debug("Invalid dsn", have=dsn, expected=settings.SENTRY_DSN)
return HttpResponse(status=400)
sentry_proxy.delay(full_body.decode())
return HttpResponse(status=204)

View File

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

View File

@ -11,7 +11,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from ua_parser import user_agent_parser
from authentik.api.authorization import OwnerPermissions
from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER, GeoIPDict
@ -103,7 +103,7 @@ class AuthenticatedSessionViewSet(
search_fields = ["user__username", "last_ip", "last_user_agent"]
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
ordering = ["user__username"]
permission_classes = [OwnerPermissions]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
def get_queryset(self):

View File

@ -2,15 +2,19 @@
from typing import Any
from django.http.response import Http404
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
@ -78,14 +82,25 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"description",
"expires",
"expiring",
"managed",
]
ordering = ["expires"]
ordering = ["identifier", "expires"]
permission_classes = [OwnerSuperuserPermissions]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)
def perform_create(self, serializer: TokenSerializer):
serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
if not self.request.user.is_superuser:
return serializer.save(
user=self.request.user,
expiring=self.request.user.attributes.get(USER_ATTRIBUTE_TOKEN_EXPIRING, True),
)
return super().perform_create(serializer)
@permission_required("authentik_core.view_token_key")
@extend_schema(

View File

@ -8,6 +8,7 @@ from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_filters.filters import BooleanFilter, CharFilter, ModelMultipleChoiceFilter
@ -89,6 +90,9 @@ class UserSerializer(ModelSerializer):
"attributes",
"uid",
]
extra_kwargs = {
"name": {"allow_blank": True},
}
class UserSelfSerializer(ModelSerializer):
@ -97,9 +101,25 @@ class UserSelfSerializer(ModelSerializer):
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
groups = ListSerializer(child=GroupSerializer(), read_only=True, source="ak_groups")
groups = SerializerMethodField()
uid = CharField(read_only=True)
@extend_schema_field(
ListSerializer(
child=inline_serializer(
"UserSelfGroups",
{"name": CharField(read_only=True), "pk": CharField(read_only=True)},
)
)
)
def get_groups(self, user: User):
"""Return only the group names a user is member of"""
for group in user.ak_groups.all():
yield {
"name": group.name,
"pk": group.pk,
}
class Meta:
model = User
@ -116,6 +136,7 @@ class UserSelfSerializer(ModelSerializer):
]
extra_kwargs = {
"is_active": {"read_only": True},
"name": {"allow_blank": True},
}
@ -207,6 +228,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset"""
queryset = User.objects.none()
ordering = ["username"]
serializer_class = UserSerializer
search_fields = ["username", "name", "is_active", "email"]
filterset_class = UsersFilter
@ -273,7 +295,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
)
group.users.add(user)
token = Token.objects.create(
identifier=f"service-account-{username}-password",
identifier=slugify(f"service-account-{username}-password"),
intent=TokenIntents.INTENT_APP_PASSWORD,
user=user,
expires=now() + timedelta(days=360),
@ -307,7 +329,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""Allow users to change information on their own profile"""
data = UserSelfSerializer(instance=User.objects.get(pk=request.user.pk), data=request.data)
if not data.is_valid():
return Response(data.errors)
return Response(data.errors, status=400)
new_user = data.save()
# If we're impersonating, we need to update that user object
# since it caches the full object

View File

@ -26,7 +26,7 @@ class Migration(migrations.Migration):
),
(
"username_link",
"Link to a user with identical username address. Can have security implications when a username is used with another source.",
"Link to a user with identical username. Can have security implications when a username is used with another source.",
),
(
"username_deny",

View File

@ -283,7 +283,7 @@ class SourceUserMatchingModes(models.TextChoices):
)
USERNAME_LINK = "username_link", _(
(
"Link to a user with identical username address. Can have security implications "
"Link to a user with identical username. Can have security implications "
"when a username is used with another source."
)
)

View File

@ -1,4 +1,6 @@
"""Test token API"""
from json import loads
from django.urls.base import reverse
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
@ -13,7 +15,8 @@ class TestTokenAPI(APITestCase):
def setUp(self) -> None:
super().setUp()
self.user = User.objects.get(username="akadmin")
self.user = User.objects.create(username="testuser")
self.admin = User.objects.get(username="akadmin")
self.client.force_login(self.user)
def test_token_create(self):
@ -55,3 +58,29 @@ class TestTokenAPI(APITestCase):
clean_expired_models.delay().get()
token.refresh_from_db()
self.assertNotEqual(key, token.key)
def test_list(self):
"""Test Token List (Test normal authentication)"""
token_should: Token = Token.objects.create(
identifier="test", expiring=False, user=self.user
)
Token.objects.create(identifier="test-2", expiring=False, user=get_anonymous_user())
response = self.client.get(reverse(("authentik_api:token-list")))
body = loads(response.content)
self.assertEqual(len(body["results"]), 1)
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
def test_list_admin(self):
"""Test Token List (Test with admin auth)"""
self.client.force_login(self.admin)
token_should: Token = Token.objects.create(
identifier="test", expiring=False, user=self.user
)
token_should_not: Token = Token.objects.create(
identifier="test-2", expiring=False, user=get_anonymous_user()
)
response = self.client.get(reverse(("authentik_api:token-list")))
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["identifier"], token_should.identifier)
self.assertEqual(body["results"][1]["identifier"], token_should_not.identifier)

View File

@ -1,7 +1,10 @@
"""NotificationTransport API Views"""
from typing import Any
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
@ -29,6 +32,14 @@ class NotificationTransportSerializer(ModelSerializer):
"""Return selected mode with a UI Label"""
return TransportMode(instance.mode).label
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure the required fields are set."""
mode = attrs.get("mode")
if mode in [TransportMode.WEBHOOK, TransportMode.WEBHOOK_SLACK]:
if "webhook_url" not in attrs or attrs.get("webhook_url", "") == "":
raise ValidationError("Webhook URL may not be empty.")
return attrs
class Meta:
model = NotificationTransport

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.7 on 2021-10-04 15:31
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0018_auto_20210911_2217"),
]
operations = [
migrations.AlterField(
model_name="notificationtransport",
name="webhook_url",
field=models.TextField(blank=True, validators=[django.core.validators.URLValidator()]),
),
]

View File

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

View File

@ -3,7 +3,6 @@ from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from timeit import default_timer
from traceback import format_tb
from typing import Any, Optional
from celery import Task
@ -42,8 +41,7 @@ class TaskResult:
def with_error(self, exc: Exception) -> "TaskResult":
"""Since errors might not always be pickle-able, set the traceback"""
self.messages.extend(format_tb(exc.__traceback__))
self.messages.append(str(exc))
self.messages.extend(exception_to_string(exc).splitlines())
return self

View File

@ -4,7 +4,13 @@ from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.events.models import Event, EventAction, Notification, NotificationSeverity
from authentik.events.models import (
Event,
EventAction,
Notification,
NotificationSeverity,
TransportMode,
)
class TestEventsAPI(APITestCase):
@ -41,3 +47,23 @@ class TestEventsAPI(APITestCase):
)
notification.refresh_from_db()
self.assertTrue(notification.seen)
def test_transport(self):
"""Test transport API"""
response = self.client.post(
reverse("authentik_api:notificationtransport-list"),
data={
"name": "foo-with",
"mode": TransportMode.WEBHOOK,
"webhook_url": "http://foo.com",
},
)
self.assertEqual(response.status_code, 201)
response = self.client.post(
reverse("authentik_api:notificationtransport-list"),
data={
"name": "foo-without",
"mode": TransportMode.WEBHOOK,
},
)
self.assertEqual(response.status_code, 400)

View File

@ -77,7 +77,7 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
final_dict = {}
for key, value in source.items():
if is_dataclass(value):
# Because asdict calls `copy.deepcopy(obj)` on everything thats not tuple/dict,
# Because asdict calls `copy.deepcopy(obj)` on everything that's not tuple/dict,
# and deepcopy doesn't work with HttpRequests (neither django nor rest_framework).
# Currently, the only dataclass that actually holds an http request is a PolicyRequest
if isinstance(value, PolicyRequest):

View File

@ -108,6 +108,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
queryset = Flow.objects.all()
serializer_class = FlowSerializer
lookup_field = "slug"
ordering = ["slug", "name"]
search_fields = ["name", "slug", "designation", "title"]
filterset_fields = ["flow_uuid", "name", "slug", "designation"]

View File

@ -57,11 +57,11 @@ class FlowPlan:
markers: list[StageMarker] = field(default_factory=list)
def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
"""Append `stage` to all stages, optionally with stage marker"""
return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None):
"""Append `stage` to all stages, optionall with stage marker"""
"""Append `stage` to all stages, optionally with stage marker"""
self.bindings.append(binding)
self.markers.append(marker or StageMarker())

View File

@ -438,7 +438,7 @@ class TestFlowExecutor(APITestCase):
# third request, this should trigger the re-evaluate
# A get request will evaluate the policies and this will return stage 4
# but it won't save it, hence we cant' check the plan
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(

View File

@ -11,7 +11,7 @@ from authentik.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and covert it to a normal dict"""
"""Get object's attributes via their serializer, and convert it to a normal dict"""
data = dict(obj.serializer(obj).data)
to_remove = (
"policies",

View File

@ -14,12 +14,7 @@ from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
PolymorphicProxySerializer,
extend_schema,
)
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from sentry_sdk import capture_exception
@ -131,12 +126,12 @@ class FlowExecutorView(APIView):
# pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session
# Early check if there's an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning(
"f(exec): Found existing plan for other flow, deleteing plan",
"f(exec): Found existing plan for other flow, deleting plan",
)
# Existing plan is deleted from session and instance
self.plan = None
@ -213,9 +208,6 @@ class FlowExecutorView(APIView):
serializers=challenge_types(),
resource_type_field_name="component",
),
404: OpenApiResponse(
description="No Token found"
), # This error can be raised by the email stage
},
request=OpenApiTypes.NONE,
parameters=[
@ -441,7 +433,7 @@ class ToDefaultFlow(View):
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
if plan.flow_pk != flow.pk.hex:
LOGGER.warning(
"f(def): Found existing plan for other flow, deleteing plan",
"f(def): Found existing plan for other flow, deleting plan",
flow_slug=flow.slug,
)
del self.request.session[SESSION_KEY_PLAN]

View File

@ -93,6 +93,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
if "exc_info" in hint:
_, exc_value, _ = hint["exc_info"]
if isinstance(exc_value, ignored_classes):
LOGGER.debug("dropping exception", exception=exc_value)
return None
if "logger" in event:
if event["logger"] in [

View File

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

View File

@ -27,7 +27,7 @@ class TestHTTP(TestCase):
token = Token.objects.create(
identifier="test", user=self.user, intent=TokenIntents.INTENT_API
)
# Invalid, non-existant token
# Invalid, non-existent token
request = self.factory.get(
"/",
**{
@ -36,7 +36,7 @@ class TestHTTP(TestCase):
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Invalid, user doesn't have permisions
# Invalid, user doesn't have permissions
request = self.factory.get(
"/",
**{

View File

@ -1,5 +1,6 @@
"""authentik lib reflection utilities"""
from importlib import import_module
from typing import Union
from django.conf import settings
@ -19,12 +20,12 @@ def all_subclasses(cls, sort=True):
return classes
def class_to_path(cls):
def class_to_path(cls: type) -> str:
"""Turn Class (Class or instance) into module path"""
return f"{cls.__module__}.{cls.__name__}"
def path_to_class(path):
def path_to_class(path: Union[str, None]) -> Union[type, None]:
"""Import module and return class"""
if not path:
return None

View File

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

View File

@ -38,6 +38,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(),
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure).lower(),
"AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
}
def _comp_env(self, container: Container) -> bool:
@ -75,6 +76,9 @@ class DockerController(BaseController):
# {'HostIp': '0.0.0.0', 'HostPort': '389'},
# {'HostIp': '::', 'HostPort': '389'}
# ]}
# If no ports are mapped (either mapping disabled, or host network)
if not container.ports:
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:
@ -98,15 +102,16 @@ class DockerController(BaseController):
"image": image_name,
"name": container_name,
"detach": True,
"ports": {
f"{port.inner_port or port.port}/{port.protocol.lower()}": port.port
for port in self.deployment_ports
},
"environment": self._get_env(),
"labels": self._get_labels(),
"restart_policy": {"Name": "unless-stopped"},
"network": self.outpost.config.docker_network,
}
if self.outpost.config.docker_map_ports:
container_args["ports"] = {
f"{port.inner_port or port.port}/{port.protocol.lower()}": str(port.port)
for port in self.deployment_ports
}
if settings.TEST:
del container_args["ports"]
del container_args["network"]
@ -164,11 +169,9 @@ class DockerController(BaseController):
self.down()
return self.up(depth + 1)
# Check that container is healthy
if (
container.status == "running"
and container.attrs.get("State", {}).get("Health", {}).get("Status", "")
!= "healthy"
):
if container.status == "running" and container.attrs.get("State", {}).get(
"Health", {}
).get("Status", "") not in ["healthy", "starting"]:
# At this point we know the config is correct, but the container isn't healthy,
# so we just restart it with the same config
if has_been_created:
@ -217,6 +220,7 @@ class DockerController(BaseController):
"AUTHENTIK_HOST": self.outpost.config.authentik_host,
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure),
"AUTHENTIK_TOKEN": self.outpost.token.key,
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
},
"labels": self._get_labels(),
}

View File

@ -10,7 +10,7 @@ from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import __version__
from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
from authentik.outposts.managed import MANAGED_OUTPOST
if TYPE_CHECKING:
@ -20,18 +20,6 @@ if TYPE_CHECKING:
T = TypeVar("T", V1Pod, V1Deployment)
class ReconcileTrigger(SentryIgnoredException):
"""Base trigger raised by child classes to notify us"""
class NeedsRecreate(ReconcileTrigger):
"""Exception to trigger a complete recreate of the Kubernetes Object"""
class NeedsUpdate(ReconcileTrigger):
"""Exception to trigger an update to the Kubernetes Object"""
class KubernetesObjectReconciler(Generic[T]):
"""Base Kubernetes Reconciler, handles the basic logic."""
@ -109,7 +97,7 @@ class KubernetesObjectReconciler(Generic[T]):
except (OpenApiException, HTTPError) as exc:
# pylint: disable=no-member
if isinstance(exc, ApiException) and exc.status == 404:
self.logger.debug("Failed to get current, assuming non-existant")
self.logger.debug("Failed to get current, assuming non-existent")
return
self.logger.debug("Other unhandled error", exc=exc)
raise exc
@ -129,7 +117,7 @@ class KubernetesObjectReconciler(Generic[T]):
raise NotImplementedError
def retrieve(self) -> T:
"""API Wrapper to retrive object"""
"""API Wrapper to retrieve object"""
raise NotImplementedError
def delete(self, reference: T):

View File

@ -17,7 +17,9 @@ from kubernetes.client import (
)
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler, NeedsUpdate
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
from authentik.outposts.controllers.k8s.utils import compare_ports
from authentik.outposts.models import Outpost
if TYPE_CHECKING:
@ -35,7 +37,10 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
self.outpost = self.controller.outpost
def reconcile(self, current: V1Deployment, reference: V1Deployment):
super().reconcile(current, reference)
compare_ports(
current.spec.template.spec.containers[0].ports,
reference.spec.template.spec.containers[0].ports,
)
if current.spec.replicas != reference.spec.replicas:
raise NeedsUpdate()
if (
@ -43,6 +48,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
!= reference.spec.template.spec.containers[0].image
):
raise NeedsUpdate()
super().reconcile(current, reference)
def get_pod_meta(self) -> dict[str, str]:
"""Get common object metadata"""
@ -89,6 +95,15 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
)
),
),
V1EnvVar(
name="AUTHENTIK_HOST_BROWSER",
value_from=V1EnvVarSource(
secret_key_ref=V1SecretKeySelector(
name=self.name,
key="authentik_host_browser",
)
),
),
V1EnvVar(
name="AUTHENTIK_TOKEN",
value_from=V1EnvVarSource(

View File

@ -5,7 +5,8 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Secret
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler, NeedsUpdate
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
if TYPE_CHECKING:
from authentik.outposts.controllers.kubernetes import KubernetesController
@ -26,7 +27,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
def reconcile(self, current: V1Secret, reference: V1Secret):
super().reconcile(current, reference)
for key in reference.data.keys():
if current.data[key] != reference.data[key]:
if key not in current.data or current.data[key] != reference.data[key]:
raise NeedsUpdate()
def get_reference_object(self) -> V1Secret:
@ -40,6 +41,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
str(self.controller.outpost.config.authentik_host_insecure)
),
"token": b64string(self.controller.outpost.token.key),
"authentik_host_browser": b64string(
self.controller.outpost.config.authentik_host_browser
),
},
)

View File

@ -4,8 +4,9 @@ from typing import TYPE_CHECKING
from kubernetes.client import CoreV1Api, V1Service, V1ServicePort, V1ServiceSpec
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler, NeedsRecreate
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.utils import compare_ports
if TYPE_CHECKING:
from authentik.outposts.controllers.kubernetes import KubernetesController
@ -19,12 +20,12 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
self.api = CoreV1Api(controller.client)
def reconcile(self, current: V1Service, reference: V1Service):
compare_ports(current.spec.ports, reference.spec.ports)
# run the base reconcile last, as that will probably raise NeedsUpdate
# after an authentik update. However the ports might have also changed during
# the update, so this causes the service to be re-created with higher
# priority than being updated.
super().reconcile(current, reference)
if len(current.spec.ports) != len(reference.spec.ports):
raise NeedsRecreate()
for port in reference.spec.ports:
if port not in current.spec.ports:
raise NeedsRecreate()
def get_reference_object(self) -> V1Service:
"""Get deployment object for outpost"""

View File

@ -0,0 +1,14 @@
"""exceptions used by the kubernetes reconciler to trigger updates"""
from authentik.lib.sentry import SentryIgnoredException
class ReconcileTrigger(SentryIgnoredException):
"""Base trigger raised by child classes to notify us"""
class NeedsRecreate(ReconcileTrigger):
"""Exception to trigger a complete recreate of the Kubernetes Object"""
class NeedsUpdate(ReconcileTrigger):
"""Exception to trigger an update to the Kubernetes Object"""

View File

@ -1,8 +1,11 @@
"""k8s utils"""
from pathlib import Path
from kubernetes.client.models.v1_container_port import V1ContainerPort
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate
def get_namespace() -> str:
"""Get the namespace if we're running in a pod, otherwise default to default"""
@ -11,3 +14,12 @@ def get_namespace() -> str:
with open(path, "r", encoding="utf8") as _namespace_file:
return _namespace_file.read()
return "default"
def compare_ports(current: list[V1ContainerPort], reference: list[V1ContainerPort]):
"""Compare ports of a list"""
if len(current) != len(reference):
raise NeedsRecreate()
for port in reference:
if port not in current:
raise NeedsRecreate()

View File

@ -30,7 +30,7 @@ class DockerInlineTLS:
return str(path)
def write(self) -> TLSConfig:
"""Create TLSConfig with Certificate Keypairs"""
"""Create TLSConfig with Certificate Key pairs"""
# So yes, this is quite ugly. But sadly, there is no clean way to pass
# docker-py (which is using requests (which is using urllib3)) a certificate
# for verification or authentication as string.

View File

@ -64,6 +64,7 @@ class OutpostConfig:
authentik_host: str = ""
authentik_host_insecure: bool = False
authentik_host_browser: str = ""
log_level: str = CONFIG.y("log_level")
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
@ -71,6 +72,7 @@ class OutpostConfig:
object_naming_template: str = field(default="ak-outpost-%(name)s")
docker_network: Optional[str] = field(default=None)
docker_map_ports: bool = field(default=True)
kubernetes_replicas: int = field(default=1)
kubernetes_namespace: str = field(default_factory=get_namespace)
@ -339,19 +341,8 @@ class Outpost(ManagedModel):
"""Username for service user"""
return f"ak-outpost-{self.uuid.hex}"
@property
def user(self) -> User:
"""Get/create user with access to all required objects"""
users = User.objects.filter(username=self.user_identifier)
if not users.exists():
user: User = User.objects.create(username=self.user_identifier)
user.set_unusable_password()
user.save()
else:
user = users.first()
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.save()
def build_user_permissions(self, user: User):
"""Create per-object and global permissions for outpost service-account"""
# To ensure the user only has the correct permissions, we delete all of them and re-add
# the ones the user needs
with transaction.atomic():
@ -395,6 +386,23 @@ class Outpost(ManagedModel):
"Updated service account's permissions",
perms=UserObjectPermission.objects.filter(user=user),
)
@property
def user(self) -> User:
"""Get/create user with access to all required objects"""
users = User.objects.filter(username=self.user_identifier)
should_create_user = not users.exists()
if should_create_user:
user: User = User.objects.create(username=self.user_identifier)
user.set_unusable_password()
user.save()
else:
user = users.first()
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.save()
if should_create_user:
self.build_user_permissions(user)
return user
@property

View File

@ -126,6 +126,7 @@ def outpost_token_ensurer(self: MonitoredTask):
all_outposts = Outpost.objects.all()
for outpost in all_outposts:
_ = outpost.token
outpost.build_user_permissions(outpost.user)
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,
@ -181,7 +182,7 @@ def outpost_post_save(model_class: str, model_pk: Any):
def outpost_send_update(model_instace: Model):
"""Send outpost update to all registered outposts, irregardless to which authentik
"""Send outpost update to all registered outposts, regardless to which authentik
instance they are connected"""
channel_layer = get_channel_layer()
if isinstance(model_instace, OutpostModel):
@ -196,7 +197,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
# Ensure token again, because this function is called when anything related to an
# OutpostModel is saved, so we can be sure permissions are right
_ = outpost.token
_ = outpost.user
outpost.build_user_permissions(outpost.user)
if not layer: # pragma: no cover
layer = get_channel_layer()
for state in OutpostState.for_outpost(outpost):
@ -208,7 +209,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
@CELERY_APP.task()
def outpost_local_connection():
"""Checks the local environment and create Service connections."""
# Explicitly check against token filename, as thats
# Explicitly check against token filename, as that's
# only present when the integration is enabled
if Path(SERVICE_TOKEN_FILENAME).exists():
LOGGER.debug("Detected in-cluster Kubernetes Config")

View File

@ -87,6 +87,7 @@ class PolicyViewSet(
"promptstage": ["isnull"],
}
search_fields = ["name"]
ordering = ["name"]
def get_queryset(self): # pragma: no cover
return Policy.objects.select_subclasses().prefetch_related("bindings", "promptstage_set")

View File

@ -3,8 +3,10 @@ from ipaddress import ip_address, ip_network
from typing import TYPE_CHECKING, Optional
from django.http import HttpRequest
from django_otp import devices_for_user
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.flows.planner import PLAN_CONTEXT_SSO
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.http import get_client_ip
@ -28,6 +30,7 @@ class PolicyEvaluator(BaseEvaluator):
self._messages = []
self._context["ak_logger"] = get_logger(policy_name)
self._context["ak_message"] = self.expr_func_message
self._context["ak_user_has_authenticator"] = self.expr_func_user_has_authenticator
self._context["ip_address"] = ip_address
self._context["ip_network"] = ip_network
self._filename = policy_name or "PolicyEvaluator"
@ -36,6 +39,19 @@ class PolicyEvaluator(BaseEvaluator):
"""Wrapper to append to messages list, which is returned with PolicyResult"""
self._messages.append(message)
def expr_func_user_has_authenticator(
self, user: User, device_type: Optional[str] = None
) -> bool:
"""Check if a user has any authenticator devices, optionally matching *device_type*"""
user_devices = devices_for_user(user)
if device_type:
for device in user_devices:
device_class = device.__class__.__name__.lower().replace("device", "")
if device_class == device_type:
return True
return False
return len(user_devices) > 0
def set_policy_request(self, request: PolicyRequest):
"""Update context based on policy request (if http request is given, update that too)"""
# update website/docs/expressions/_objects.md

View File

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

View File

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

View File

@ -14,7 +14,7 @@ from authentik.policies.types import PolicyRequest
def clear_policy_cache():
"""Ensure no policy-related keys are stil cached"""
"""Ensure no policy-related keys are still cached"""
keys = cache.keys("policy_*")
cache.delete(keys)

View File

@ -1,16 +1,15 @@
"""LDAP Provider Docker Contoller"""
"""LDAP Provider Docker Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.models import DockerServiceConnection, Outpost
class LDAPDockerController(DockerController):
"""LDAP Provider Docker Contoller"""
"""LDAP Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
DeploymentPort(9300, "http-metrics", "tcp", 9300),
]

View File

@ -1,11 +1,11 @@
"""LDAP Provider Kubernetes Contoller"""
"""LDAP Provider Kubernetes Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost
class LDAPKubernetesController(KubernetesController):
"""LDAP Provider Kubernetes Contoller"""
"""LDAP Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection)

View File

@ -153,7 +153,7 @@ def protected_resource_view(scopes: list[str]):
if not set(scopes).issubset(set(token.scope)):
LOGGER.warning(
"Scope missmatch.",
"Scope mismatch.",
required=set(scopes),
token_has=set(token.scope),
)

View File

@ -33,7 +33,7 @@ class UserInfoView(View):
for scope in ScopeMapping.objects.filter(scope_name__in=scopes).order_by("scope_name"):
if scope.description != "":
scope_descriptions.append({"id": scope.scope_name, "name": scope.description})
# GitHub Compatibility Scopes are handeled differently, since they required custom paths
# GitHub Compatibility Scopes are handled differently, since they required custom paths
# Hence they don't exist as Scope objects
github_scope_map = {
SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"),

View File

@ -1,5 +1,5 @@
"""ProxyProvider API Views"""
from typing import Any
from typing import Any, Optional
from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError
@ -10,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.views.provider import ProviderInfoView
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
@ -106,6 +107,16 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"""Proxy provider serializer for outposts"""
oidc_configuration = SerializerMethodField()
token_validity = SerializerMethodField()
@extend_schema_field(OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information"""
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
def get_token_validity(self, obj: ProxyProvider) -> Optional[float]:
"""Get token validity as second count"""
return timedelta_from_string(obj.token_validity).total_seconds()
class Meta:
@ -127,13 +138,9 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"basic_auth_user_attribute",
"mode",
"cookie_domain",
"token_validity",
]
@extend_schema_field(OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information"""
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
class ProxyOutpostConfigViewSet(ReadOnlyModelViewSet):
"""ProxyProvider Viewset"""

View File

@ -1,4 +1,4 @@
"""Proxy Provider Docker Contoller"""
"""Proxy Provider Docker Controller"""
from urllib.parse import urlparse
from authentik.outposts.controllers.base import DeploymentPort
@ -8,13 +8,12 @@ from authentik.providers.proxy.models import ProxyProvider
class ProxyDockerController(DockerController):
"""Proxy Provider Docker Contoller"""
"""Proxy Provider Docker Controller"""
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(9000, "http", "tcp"),
DeploymentPort(9300, "http-metrics", "tcp"),
DeploymentPort(9443, "https", "tcp"),
]
@ -30,6 +29,11 @@ class ProxyDockerController(DockerController):
labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/"
labels[
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
] = "/akprox/ping"
labels[
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
] = "9300"
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "9000"
return labels

View File

@ -14,11 +14,8 @@ from kubernetes.client import (
from kubernetes.client.models.networking_v1beta1_ingress_rule import NetworkingV1beta1IngressRule
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsRecreate,
NeedsUpdate,
)
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
if TYPE_CHECKING:
@ -63,8 +60,15 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
have_hosts_tls = []
if current.spec.tls:
for tls_config in current.spec.tls:
if tls_config and tls_config.hosts:
if not tls_config:
continue
if tls_config.hosts:
have_hosts_tls += tls_config.hosts
if (
tls_config.secret_name
!= self.controller.outpost.config.kubernetes_ingress_secret_name
):
raise NeedsUpdate()
have_hosts_tls.sort()
if have_hosts != expected_hosts:

View File

@ -6,7 +6,8 @@ from dacite import from_dict
from kubernetes.client import ApiextensionsV1Api, CustomObjectsApi
from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler, NeedsUpdate
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
if TYPE_CHECKING:
@ -109,11 +110,18 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik",
authResponseHeaders=[
"Set-Cookie",
# Legacy headers, remove after 2022.1
"X-Auth-Username",
"X-Auth-Groups",
"X-Forwarded-Email",
"X-Forwarded-Preferred-Username",
"X-Forwarded-User",
# New headers, unique prefix
"X-authentik-username",
"X-authentik-groups",
"X-authentik-email",
"X-authentik-name",
"X-authentik-uid",
],
trustForwardHeader=True,
)

View File

@ -1,4 +1,4 @@
"""Proxy Provider Kubernetes Contoller"""
"""Proxy Provider Kubernetes Controller"""
from authentik.outposts.controllers.base import DeploymentPort
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost
@ -7,7 +7,7 @@ from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareR
class ProxyKubernetesController(KubernetesController):
"""Proxy Provider Kubernetes Contoller"""
"""Proxy Provider Kubernetes Controller"""
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection)

View File

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

View File

@ -3,10 +3,20 @@ import os
from logging.config import dictConfig
from celery import Celery
from celery.signals import after_task_publish, setup_logging, task_postrun, task_prerun
from celery.signals import (
after_task_publish,
setup_logging,
task_failure,
task_internal_error,
task_postrun,
task_prerun,
)
from django.conf import settings
from structlog.stdlib import get_logger
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
@ -43,6 +53,18 @@ def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
LOGGER.debug("Task finished", task_id=task_id, task_name=task.__name__, state=state)
# pylint: disable=unused-argument
@task_failure.connect
@task_internal_error.connect
def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
"""Create system event for failed task"""
from authentik.events.models import Event, EventAction
LOGGER.warning("Task failure", exception=exception)
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys

View File

@ -59,14 +59,14 @@ class MetricsView(View):
class LiveView(View):
"""View for liveness probe, always returns Http 201"""
"""View for liveness probe, always returns Http 204"""
def dispatch(self, request: HttpRequest) -> HttpResponse:
return HttpResponse(status=201)
return HttpResponse(status=204)
class ReadyView(View):
"""View for readiness probe, always returns Http 201, unless sql or redis is down"""
"""View for readiness probe, always returns Http 204, unless sql or redis is down"""
def dispatch(self, request: HttpRequest) -> HttpResponse:
try:
@ -79,4 +79,4 @@ class ReadyView(View):
redis_conn.ping()
except RedisError:
return HttpResponse(status=503)
return HttpResponse(status=201)
return HttpResponse(status=204)

View File

@ -529,7 +529,7 @@ for _app in INSTALLED_APPS:
if "apps" in _app:
_app = ".".join(_app.split(".")[:-2])
try:
app_settings = importlib.import_module("%s.settings" % _app)
app_settings = importlib.import_module(f"{_app}.settings")
INSTALLED_APPS.extend(getattr(app_settings, "INSTALLED_APPS", []))
MIDDLEWARE.extend(getattr(app_settings, "MIDDLEWARE", []))
AUTHENTICATION_BACKENDS.extend(getattr(app_settings, "AUTHENTICATION_BACKENDS", []))

View File

@ -1,12 +1,11 @@
"""Source API Views"""
from typing import Any
from django.http.response import Http404
from django.utils.text import slugify
from django_filters.filters import AllValuesMultipleFilter
from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
@ -19,6 +18,9 @@ from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.events.monitored_tasks import TaskInfo
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
class LDAPSourceSerializer(SourceSerializer):
@ -95,19 +97,24 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
responses={
200: TaskSerializer(many=False),
404: OpenApiResponse(description="Task not found"),
200: TaskSerializer(many=True),
}
)
@action(methods=["GET"], detail=True)
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument
def sync_status(self, request: Request, slug: str) -> Response:
"""Get source's sync status"""
source = self.get_object()
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}")
if not task:
raise Http404
return Response(TaskSerializer(task, many=False).data)
results = []
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}-{sync_class.__name__}")
if task:
results.append(task)
return Response(TaskSerializer(results, many=True).data)
class LDAPPropertyMappingSerializer(PropertyMappingSerializer):

View File

@ -119,7 +119,7 @@ class LDAPPasswordChanger:
return True
def ad_password_complexity(self, password: str, user: Optional[User] = None) -> bool:
"""Check if password matches Active direcotry password policies
"""Check if password matches Active directory password policies
https://docs.microsoft.com/en-us/windows/security/threat-protection/
security-policy-settings/password-must-meet-complexity-requirements

View File

@ -4,7 +4,7 @@ from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"sources_ldap_sync": {
"task": "authentik.sources.ldap.tasks.ldap_sync_all",
"schedule": crontab(minute="*/60"), # Run every hour
"schedule": crontab(minute="*/120"), # Run every other hour
"options": {"queue": "authentik_scheduled"},
}
}

View File

@ -11,8 +11,12 @@ from authentik.core.models import User
from authentik.core.signals import password_changed
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.lib.utils.reflection import class_to_path
from authentik.sources.ldap.models import LDAPSource
from authentik.sources.ldap.password import LDAPPasswordChanger
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
from authentik.sources.ldap.tasks import ldap_sync
from authentik.stages.prompt.signals import password_validate
@ -21,8 +25,20 @@ from authentik.stages.prompt.signals import password_validate
# pylint: disable=unused-argument
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
"""Ensure that source is synced on save (if enabled)"""
if instance.enabled:
ldap_sync.delay(instance.pk)
if not instance.enabled:
return
# Don't sync sources when they don't have any property mappings. This will only happen if:
# - the user forgets to set them or
# - the source is newly created, this is the first save event
# and the mappings are created with an m2m event
if not instance.property_mappings.exists() or not instance.property_mappings_group.exists():
return
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
ldap_sync.delay(instance.pk, class_to_path(sync_class))
@receiver(password_validate)
@ -39,7 +55,7 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any],
password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
)
if not passing:
raise ValidationError(_("Password does not match Active Direcory Complexity."))
raise ValidationError(_("Password does not match Active Directory Complexity."))
@receiver(password_changed)

View File

@ -17,11 +17,18 @@ class BaseLDAPSynchronizer:
_source: LDAPSource
_logger: BoundLogger
_messages: list[str]
def __init__(self, source: LDAPSource):
self._source = source
self._messages = []
self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
@property
def messages(self) -> list[str]:
"""Get all UI messages"""
return self._messages
@property
def base_dn_users(self) -> str:
"""Shortcut to get full base_dn for user lookups"""
@ -36,6 +43,11 @@ class BaseLDAPSynchronizer:
return f"{self._source.additional_group_dn},{self._source.base_dn}"
return self._source.base_dn
def message(self, *args, **kwargs):
"""Add message that is later added to the System Task and shown to the user"""
self._messages.append(" ".join(args))
self._logger.warning(*args, **kwargs)
def sync(self) -> int:
"""Sync function, implemented in subclass"""
raise NotImplementedError()

View File

@ -15,7 +15,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
def sync(self) -> int:
"""Iterate over all LDAP Groups and create authentik_core.Group instances"""
if not self._source.sync_groups:
self._logger.warning("Group syncing is disabled for this Source")
self.message("Group syncing is disabled for this Source")
return -1
groups = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_groups,
@ -28,8 +28,8 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
attributes = group.get("attributes", {})
group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn"))))
if self._source.object_uniqueness_field not in attributes:
self._logger.warning(
"Cannot find uniqueness Field in attributes",
self.message(
f"Cannot find uniqueness field in attributes: '{group_dn}",
attributes=attributes.keys(),
dn=group_dn,
)

View File

@ -39,11 +39,17 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
if not ak_group:
continue
membership_mapping_attribute = LDAP_DISTINGUISHED_NAME
if self._source.group_membership_field == "memberUid":
# If memberships are based on the posixGroup's 'memberUid'
# attribute we use the RDN instead of the FDN to lookup members.
membership_mapping_attribute = LDAP_UNIQUENESS
users = User.objects.filter(
Q(**{f"attributes__{LDAP_DISTINGUISHED_NAME}__in": members})
Q(**{f"attributes__{membership_mapping_attribute}__in": members})
| Q(
**{
f"attributes__{LDAP_DISTINGUISHED_NAME}__isnull": True,
f"attributes__{membership_mapping_attribute}__isnull": True,
"ak_groups__in": [ak_group],
}
)
@ -62,8 +68,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
# group_uniq might be a single string or an array with (hopefully) a single string
if isinstance(group_uniq, list):
if len(group_uniq) < 1:
self._logger.warning(
"Group does not have a uniqueness attribute.",
self.message(
f"Group does not have a uniqueness attribute: '{group_dn}'",
group=group_dn,
)
return None
@ -71,8 +77,8 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
if group_uniq not in self.group_cache:
groups = Group.objects.filter(**{f"attributes__{LDAP_UNIQUENESS}": group_uniq})
if not groups.exists():
self._logger.warning(
"Group does not exist in our DB yet, run sync_groups first.",
self.message(
f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
group=group_dn,
)
return None

View File

@ -10,6 +10,7 @@ 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
class UserLDAPSynchronizer(BaseLDAPSynchronizer):
@ -18,7 +19,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
def sync(self) -> int:
"""Iterate over all LDAP Users and create authentik_core.User instances"""
if not self._source.sync_users:
self._logger.warning("User syncing is disabled for this Source")
self.message("User syncing is disabled for this Source")
return -1
users = self._source.connection.extend.standard.paged_search(
search_base=self.base_dn_users,
@ -31,8 +32,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
attributes = user.get("attributes", {})
user_dn = self._flatten(user.get("entryDN", user.get("dn")))
if self._source.object_uniqueness_field not in attributes:
self._logger.warning(
"Cannot find uniqueness Field in attributes",
self.message(
f"Cannot find uniqueness field in attributes: '{user_dn}",
attributes=attributes.keys(),
dn=user_dn,
)
@ -66,6 +67,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
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,
@ -74,4 +76,8 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
)
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()
return user_count

View File

View File

@ -0,0 +1,32 @@
"""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

@ -1,9 +1,12 @@
"""LDAP Sync tasks"""
from typing import Optional
from django.utils.text import slugify
from ldap3.core.exceptions import LDAPException
from structlog.stdlib import get_logger
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.lib.utils.reflection import class_to_path, path_to_class
from authentik.root.celery import CELERY_APP
from authentik.sources.ldap.models import LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
@ -17,11 +20,19 @@ LOGGER = get_logger()
def ldap_sync_all():
"""Sync all sources"""
for source in LDAPSource.objects.filter(enabled=True):
ldap_sync.delay(source.pk)
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
ldap_sync.delay(source.pk, class_to_path(sync_class))
@CELERY_APP.task(bind=True, base=MonitoredTask)
def ldap_sync(self: MonitoredTask, source_pk: str):
@CELERY_APP.task(
bind=True, base=MonitoredTask, soft_time_limit=60 * 60 * 2, task_time_limit=60 * 60 * 2
)
# TODO: remove Optional[str] in 2021.10
def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: Optional[str] = None):
"""Synchronization of an LDAP Source"""
self.result_timeout_hours = 2
try:
@ -30,17 +41,15 @@ def ldap_sync(self: MonitoredTask, source_pk: str):
# Because the source couldn't be found, we don't have a UID
# to set the state with
return
self.set_uid(slugify(source.name))
if not sync_class:
return
sync = path_to_class(sync_class)
self.set_uid(f"{slugify(source.name)}-{sync.__name__}")
try:
messages = []
for sync_class in [
UserLDAPSynchronizer,
GroupLDAPSynchronizer,
MembershipLDAPSynchronizer,
]:
sync_inst = sync_class(source)
count = sync_inst.sync()
messages.append(f"Synced {count} objects from {sync_class.__name__}")
sync_inst = sync(source)
count = sync_inst.sync()
messages = sync_inst.messages
messages.append(f"Synced {count} objects.")
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,

View File

@ -2,6 +2,8 @@
from ldap3 import MOCK_SYNC, OFFLINE_AD_2012_R2, Connection, Server
from authentik.sources.ldap.sync.vendor.ad import UserAccountControl
def mock_ad_connection(password: str) -> Connection:
"""Create mock AD connection"""
@ -54,6 +56,8 @@ def mock_ad_connection(password: str) -> Connection:
"objectSid": "user0",
"objectClass": "person",
"distinguishedName": "cn=user0,ou=users,dc=goauthentik,dc=io",
"userAccountControl": UserAccountControl.ACCOUNTDISABLE
+ UserAccountControl.NORMAL_ACCOUNT,
},
)
# User without SID

View File

@ -77,5 +77,24 @@ def mock_slapd_connection(password: str) -> Connection:
"objectClass": "person",
},
)
# Group with posixGroup and memberUid
connection.strategy.add_entry(
"cn=group-posix,ou=groups,dc=goauthentik,dc=io",
{
"cn": "group-posix",
"objectClass": "posixGroup",
"memberUid": ["user-posix"],
},
)
# User with posixAccount
connection.strategy.add_entry(
"cn=user-posix,ou=users,dc=goauthentik,dc=io",
{
"userPassword": password,
"uid": "user-posix",
"cn": "user-posix",
"objectClass": "posixAccount",
},
)
connection.bind()
return connection

View File

@ -72,7 +72,8 @@ class LDAPSyncTests(TestCase):
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)
user_sync.sync()
self.assertTrue(User.objects.filter(username="user0_sn").exists())
user = User.objects.filter(username="user0_sn").first()
self.assertFalse(user.is_active)
self.assertFalse(User.objects.filter(username="user1_sn").exists())
def test_sync_users_openldap(self):
@ -136,6 +137,34 @@ class LDAPSyncTests(TestCase):
group = Group.objects.filter(name="group1")
self.assertTrue(group.exists())
def test_sync_groups_openldap_posix_group(self):
"""Test posix group sync"""
self.source.object_uniqueness_field = "cn"
self.source.group_membership_field = "memberUid"
self.source.user_object_filter = "(objectClass=posixAccount)"
self.source.group_object_filter = "(objectClass=posixGroup)"
self.source.property_mappings.set(
LDAPPropertyMapping.objects.filter(
Q(managed__startswith="goauthentik.io/sources/ldap/default")
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
)
)
self.source.property_mappings_group.set(
LDAPPropertyMapping.objects.filter(managed="goauthentik.io/sources/ldap/openldap-cn")
)
self.source.save()
connection = PropertyMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)
user_sync.sync()
group_sync = GroupLDAPSynchronizer(self.source)
group_sync.sync()
membership_sync = MembershipLDAPSynchronizer(self.source)
membership_sync.sync()
# Test if membership mapping based on memberUid works.
posix_group = Group.objects.filter(name="group-posix").first()
self.assertTrue(posix_group.users.filter(name="user-posix").exists())
def test_tasks_ad(self):
"""Test Scheduled tasks"""
self.source.property_mappings.set(

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ class OAuthSource(Source):
max_length=255,
null=True,
verbose_name=_("Access Token URL"),
help_text=_("URL used by authentik to retrive tokens."),
help_text=_("URL used by authentik to retrieve tokens."),
)
profile_url = models.CharField(
max_length=255,

View File

@ -24,7 +24,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="plexsource",
name="plex_token",
field=models.TextField(default="", help_text="Plex token used to check firends"),
field=models.TextField(default="", help_text="Plex token used to check friends"),
),
migrations.AlterField(
model_name="plexsource",

View File

@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="plexsource",
name="plex_token",
field=models.TextField(help_text="Plex token used to check firends"),
field=models.TextField(help_text="Plex token used to check friends"),
),
]

View File

@ -50,7 +50,7 @@ class PlexSource(Source):
default=True,
help_text=_("Allow friends to authenticate, even if you don't share a server."),
)
plex_token = models.TextField(help_text=_("Plex token used to check firends"))
plex_token = models.TextField(help_text=_("Plex token used to check friends"))
@property
def component(self) -> str:

View File

@ -54,7 +54,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
class CaptchaStageView(ChallengeStageView):
"""Simple captcha checker, logic is handeled in django-captcha module"""
"""Simple captcha checker, logic is handled in django-captcha module"""
response_class = CaptchaChallengeResponse

View File

@ -43,6 +43,7 @@ class EmailStageSerializer(StageSerializer):
"token_expiry",
"subject",
"template",
"activate_user_on_success",
]
extra_kwargs = {"password": {"write_only": True}}
@ -65,6 +66,7 @@ class EmailStageViewSet(UsedByMixin, ModelViewSet):
"token_expiry",
"subject",
"template",
"activate_user_on_success",
]
ordering = ["name"]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.7 on 2021-10-04 16:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_email", "0003_auto_20210404_1054"),
]
operations = [
migrations.AddField(
model_name="emailstage",
name="activate_user_on_success",
field=models.BooleanField(
default=False, help_text="Activate users upon completion of stage."
),
),
]

View File

@ -71,6 +71,10 @@ class EmailStage(Stage):
timeout = models.IntegerField(default=10)
from_address = models.EmailField(default="system@authentik.local")
activate_user_on_success = models.BooleanField(
default=False, help_text=_("Activate users upon completion of stage.")
)
token_expiry = models.IntegerField(
default=30, help_text=_("Time in minutes the token sent is valid.")
)

View File

@ -3,9 +3,9 @@ from datetime import timedelta
from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.text import slugify
from django.utils.timezone import now
from django.utils.translation import gettext as _
from rest_framework.fields import CharField
@ -22,7 +22,7 @@ from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger()
QS_KEY_TOKEN = "token" # nosec
QS_KEY_TOKEN = "etoken" # nosec
PLAN_CONTEXT_EMAIL_SENT = "email_sent"
@ -65,7 +65,7 @@ class EmailStageView(ChallengeStageView):
) # + 1 because django timesince always rounds down
token_filters = {
"user": pending_user,
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
"identifier": slugify(f"ak-email-stage-{current_stage.name}-{pending_user}"),
}
# Don't check for validity here, we only care if the token exists
tokens = Token.objects.filter(**token_filters)
@ -99,10 +99,16 @@ class EmailStageView(ChallengeStageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
# Check if the user came back from the email link to verify
if QS_KEY_TOKEN in request.session.get(SESSION_KEY_GET, {}):
token = get_object_or_404(Token, key=request.session[SESSION_KEY_GET][QS_KEY_TOKEN])
tokens = Token.filter_not_expired(key=request.session[SESSION_KEY_GET][QS_KEY_TOKEN])
if not tokens.exists():
return self.executor.stage_invalid(_("Invalid token"))
token = tokens.first()
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user
token.delete()
messages.success(request, _("Successfully verified Email."))
if self.executor.current_stage.activate_user_on_success:
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].is_active = True
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER].save()
return self.executor.stage_ok()
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
LOGGER.debug("No pending user")
@ -118,7 +124,7 @@ class EmailStageView(ChallengeStageView):
challenge = EmailChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"title": "Email sent.",
"title": _("Email sent."),
}
)
return challenge

View File

@ -31,6 +31,7 @@ class TestEmailStage(APITestCase):
)
self.stage = EmailStage.objects.create(
name="email",
activate_user_on_success=True,
)
self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
@ -84,6 +85,8 @@ class TestEmailStage(APITestCase):
"""Test with token"""
# Make sure token exists
self.test_pending_user()
self.user.is_active = False
self.user.save()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
@ -125,3 +128,4 @@ class TestEmailStage(APITestCase):
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(plan.context[PLAN_CONTEXT_PENDING_USER], self.user)
self.assertTrue(plan.context[PLAN_CONTEXT_PENDING_USER].is_active)

View File

@ -15,7 +15,8 @@ from authentik.stages.invitation.signals import invitation_used
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
INVITATION_TOKEN_KEY = "token" # nosec
INVITATION_TOKEN_KEY_CONTEXT = "token" # nosec
INVITATION_TOKEN_KEY = "itoken" # nosec
INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
@ -29,10 +30,14 @@ class InvitationStageView(StageView):
def get_token(self) -> Optional[str]:
"""Get token from saved get-arguments or prompt_data"""
# Check for ?token= and ?itoken=
if INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY]
if INVITATION_TOKEN_KEY_CONTEXT in self.request.session.get(SESSION_KEY_GET, {}):
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY_CONTEXT]
# Check for {'token': ''} in the context
if INVITATION_TOKEN_KEY_CONTEXT in self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {}):
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY_CONTEXT]
return None
def get(self, request: HttpRequest) -> HttpResponse:

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