Compare commits

...

180 Commits

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

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

* Update index.md

Updated as discussed in PR

* Update index.md

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

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

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

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

closes #1969

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-21 09:27:57 +01:00
2e50532518 build(deps): bump codemirror from 5.64.0 to 5.65.0 in /web (#1973) 2021-12-21 09:14:27 +01:00
1936ddfecb build(deps): bump @typescript-eslint/parser from 5.7.0 to 5.8.0 in /web (#1975) 2021-12-21 09:13:50 +01:00
4afef46cb8 ci: improve restore after switching to stable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-20 22:47:06 +01:00
92b4244e81 providers/proxy: update traefik regex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

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

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

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

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

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

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

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

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

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

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

* ci: add initial ci to build outpost as binary

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

* ci: fix typo, build web

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

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

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

* ci: ensure latest go is used

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

* ci: split e2e tests into two halves

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

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

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

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

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

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

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-16 16:56:43 +01:00
3bf53b2db1 root: update security
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 16:54:48 +01:00
f33190caa5 release: 2021.12.1 2021-12-16 15:48:59 +01:00
741822424a Merge branch 'master' into version-2021.12 2021-12-16 15:48:53 +01:00
0ca6fbb224 website/docs: final 2021.12.1 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 15:48:47 +01:00
Sem
f72b652b24 website/integrations: Updated bookstack integration docs page (#1942)
In some cases one might need to define the full SAML property to enable proper group sync. (see: https://github.com/BookStackApp/BookStack/issues/3109 )
2021-12-16 14:23:00 +01:00
0a2c1eb419 web/elements: fix linting error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:27:08 +01:00
eb9593a847 web/elements: close notification drawer when clearing all notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:23:44 +01:00
7c71c52791 web/admin: add sidebar to applications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:23:30 +01:00
59493c02c4 web/elements: pass full Markdown object to ak-markdown, get title from metadata
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:18:43 +01:00
83089b47d3 web/elements: add Markdown component to improve rendering
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:10:46 +01:00
103e723d8c web/elements: add support for sidebar on table page
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 12:10:28 +01:00
7d6e88061f outposts: check if hub from context is set and fallback
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 11:19:57 +01:00
f8aab40e3e internal: cleanup duplicate and redundant code, properly set sentry SDK scope settings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 11:00:19 +01:00
5123bc1316 root: add sponsors to readme
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 10:30:13 +01:00
30e8408e85 web/admin: fix notification unread colours not matching on user and admin interface
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 10:17:17 +01:00
bb34474101 web/admin: fix stage related flows not being shown in a list
closes #1941

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-16 10:13:10 +01:00
a105760123 events: improve app lookup for event creation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 16:46:02 +01:00
f410a77010 lifecycle: add -Ofair to celery
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 16:44:09 +01:00
6ff8fdcc49 root: enable threading integration in sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 15:49:08 +01:00
50ca3dc772 core: fix error when attempting to provider from cached application
closes #1940

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 15:11:13 +01:00
2a09fc0ae2 release: 2021.12.1-rc5 2021-12-15 10:21:29 +01:00
fbb6756488 Merge branch 'master' into version-2021.12 2021-12-15 10:16:05 +01:00
f45fb2eac0 website/docs: prepare 2021.12.1-rc5
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 10:15:58 +01:00
7b8cde17e6 web/admin: show warning when deleting currently logged in user
closes #1937

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 10:11:35 +01:00
186634fc67 build(deps): bump @patternfly/patternfly from 4.159.1 to 4.164.2 in /web (#1938) 2021-12-15 08:58:46 +01:00
c84b1b7997 build(deps): bump goauthentik.io/api from 0.2021104.13 to 0.2021104.17 (#1939) 2021-12-15 08:58:30 +01:00
6e83467481 web/flows: fix error when attempting to enroll new webauthn device
closes #1936

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-15 00:24:46 +01:00
72db17f23b stages/identification: fix miscalculated sleep
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 23:31:08 +01:00
ee4e176039 web/admin: fix invalid display for LDAP Source sync status
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 23:00:45 +01:00
e18e681c2b events: dont store full backtrace in systemtask
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 22:55:38 +01:00
10fe67e08d sources/ldap: fix incorrect task names being referenced, use source native slug
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 22:53:14 +01:00
fc1db83be7 web: Update Web API Client version (#1935)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-14 22:19:46 +01:00
3740e65906 web/admin: add dashboard with user creation/login statistics
closes #1867

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 22:08:41 +01:00
30386cd899 events: add custom manager with helpers for metrics
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 21:49:33 +01:00
64a10e9a46 events: fix schema for top_per_user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 21:08:15 +01:00
77d6242cce web/admin: fix extra closing element
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 20:53:25 +01:00
9a86dcaec3 web: Update Web API Client version (#1934)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-14 16:26:21 +01:00
0b00768b84 events: add flow_execution event type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 16:13:51 +01:00
d162c79373 flows: fix wrong exception being caught in flow inspector
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 16:06:00 +01:00
05db352a0f web: add link to open API Browser for API Drawer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 16:03:42 +01:00
5bf3d7fe02 web: Update Web API Client version (#1933)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-14 15:59:26 +01:00
1ae1cbebf4 web/admin: re-organise sidebar items
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:57:03 +01:00
8c16dfc478 stages/invitation: use GroupMemberSerializer serializer to prevent all of the user's groups and their users from being returned
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:56:13 +01:00
c6a3286e4c web/admin: update overview page
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:23:32 +01:00
44cfd7e5b0 web: accept header as slot in PageHeader
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:23:20 +01:00
210d4c5058 web: add helper to navigate with params
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:23:02 +01:00
6b39d616b1 web/elements: allow aggregate cards' elements to not be centered
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 15:22:52 +01:00
32ace1bece crypto: add additional validation before importing a certificate
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 14:49:25 +01:00
54f893b84f flows: add additional sentry spans
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 11:59:36 +01:00
b5685ec072 outposts: set sentry-trace on API requests to match them to the outer transaction
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 11:50:31 +01:00
5854833240 stages/authenticator_webauthn: fix migrations for different choices
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 11:06:46 +01:00
4b2437a6f1 stages/authenticator_webauthn: use correct choices
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 10:51:34 +01:00
2981ac7b10 tests/e2e: use ghcr for e2e tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 10:36:47 +01:00
59a51c859a stages/authenticator_webauthn: add migration
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 10:09:35 +01:00
47bab6c182 web: Update Web API Client version (#1932)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: BeryJu <BeryJu@users.noreply.github.com>
2021-12-14 10:02:50 +01:00
4e6714fffe stages/authenticator_webauthn: make user_verification configurable
closes #1921

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 09:58:20 +01:00
aa6b595545 root: bump python dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 09:45:36 +01:00
0131b1f6cc sources/oauth: fix wrong redirect URL being generated
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 09:34:47 +01:00
9f53c359dd build(deps): bump typescript from 4.5.3 to 4.5.4 in /web (#1926) 2021-12-14 08:31:56 +01:00
28e4dba3e8 build(deps): bump @babel/core from 7.16.0 to 7.16.5 in /web (#1929) 2021-12-14 08:31:36 +01:00
2afd46e1df build(deps): bump @babel/plugin-transform-runtime in /web (#1922) 2021-12-14 08:31:28 +01:00
f5991b19be build(deps): bump @babel/preset-typescript from 7.16.0 to 7.16.5 in /web (#1925) 2021-12-14 08:31:19 +01:00
5cc75cb25c build(deps): bump @typescript-eslint/parser from 5.6.0 to 5.7.0 in /web (#1923) 2021-12-14 08:31:04 +01:00
68c1df2d39 build(deps): bump @rollup/plugin-node-resolve in /web (#1924) 2021-12-14 08:30:35 +01:00
c83724f45c build(deps): bump @babel/preset-env from 7.16.4 to 7.16.5 in /web (#1930) 2021-12-14 08:29:56 +01:00
5f91c150df build(deps): bump @babel/plugin-proposal-decorators in /web (#1927) 2021-12-14 08:29:36 +01:00
0bfe999442 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1928) 2021-12-14 08:29:13 +01:00
58440b16c4 build(deps): bump goauthentik.io/api from 0.2021104.11 to 0.2021104.13 (#1931) 2021-12-14 08:28:42 +01:00
57757a2ff5 web: Update Web API Client version (#1920) 2021-12-14 01:11:32 +01:00
2993f506a7 sources/oauth: implement apple native sign-in using the apple JS SDK
closes #1881

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-14 00:40:29 +01:00
e4841d54a1 *: migrate ui_* properties to functions to allow context being passed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 23:56:35 +01:00
4f05dcec89 sources/oauth: allow oauth types to override their login button challenge
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 23:45:11 +01:00
ede6bcd31e *: remove debug statements from tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 23:41:08 +01:00
728c8e994d sources/oauth: strip parts of custom apple client_id
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 23:26:00 +01:00
5290b64415 web: Update Web API Client version
commit 96533a743c
Author: BeryJu <BeryJu@users.noreply.github.com>
Date:   Mon Dec 13 20:50:45 2021 +0000

    web: Update Web API Client version

    Signed-off-by: GitHub <noreply@github.com>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 22:53:28 +01:00
fec6de1ba2 providers/oauth2: add additional logging to show with token path is taken
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 22:49:42 +01:00
69678dcfa6 providers/oauth2: use generate_key instead of uuid4
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 22:13:20 +01:00
4911a243ff sources/oauth: add initial okta type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1910
2021-12-13 21:48:59 +01:00
70316b37da web/admin: only show source name not description
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 21:48:45 +01:00
307cb94e3b website: add initial redirect (#1918)
* website: add initial redirect

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

* website: add integrations too

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

* website: add docs to netlify config

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

* website: use splats correctly

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

* add status

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 20:42:31 +00:00
ace53a8fa5 root: remove lxml version workaround
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 21:08:50 +01:00
0544dc3f83 web: use correct transaction names for web
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 21:03:30 +01:00
708ff300a3 website: remove remaining /index URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 19:01:16 +01:00
4e63f0f215 core: add fallback for missing sentry trace
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 18:06:01 +01:00
141481df3a web: send sentry-trace header in API requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 17:41:11 +01:00
29241cc287 core: always inject sentry trace into template
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 17:41:00 +01:00
e81e97d404 root: add .python-version so dependabot doesn't use broken python 3.10.0
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 17:23:42 +01:00
a5182e5c24 root: custom sentry-sdk, attempt #3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 17:00:18 +01:00
cf5ff6e160 outposts: reset backoff after successful connect
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:38:48 +01:00
f2b3a2ec91 providers/saml: optimise excessive queries to user when evaluating attributes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:38:38 +01:00
69780c67a9 lib: set evaluation span's description based on filename
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:32:01 +01:00
ac9cf590bc *: use prefixed span names
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:18:42 +01:00
cb6edcb198 core: set tag with request ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:15:27 +01:00
8eecc28c3c events: add sentry for geoip
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:15:20 +01:00
10b16bc36a outposts: add description to span
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 16:12:14 +01:00
2fe88cfea9 root: don't stale enhancement/confirmed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 15:51:30 +01:00
caab396b56 web/admin: improve wording for froward_auth, don't show setup when using proxy mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 15:36:05 +01:00
5f0f4284a2 web/admin: fix rendering for applications on view page
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 15:27:28 +01:00
c11be2284d outposts/proxy: also set max length for redis backend
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 15:05:55 +01:00
aa321196d7 outposts/proxy: fix securecookie: the value is too long again, since it can happen even with filesystem storage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 13:33:20 +01:00
ff03db61a8 web/admin: fix rendering of applications with custom icon
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 13:21:14 +01:00
f3b3ce6572 website/docs: add 2021.12.1-rc4 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-12-13 12:56:34 +01:00
222 changed files with 4365 additions and 2334 deletions

View File

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

1
.github/stale.yml vendored
View File

@ -7,6 +7,7 @@ exemptLabels:
- pinned - pinned
- security - security
- pr_wanted - pr_wanted
- enhancement/confirmed
# Comment to post when marking an issue as stale. Set to `false` to disable # Comment to post when marking an issue as stale. Set to `false` to disable
markComment: > markComment: >
This issue has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had

View File

@ -47,7 +47,7 @@ jobs:
env: env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }} INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh run: scripts/ci_prepare.sh
- name: run pylint - name: run job
run: pipenv run make ci-${{ matrix.job }} run: pipenv run make ci-${{ matrix.job }}
test-migrations: test-migrations:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -86,7 +86,11 @@ jobs:
path: ~/.local/share/virtualenvs path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }} key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: checkout stable - name: checkout stable
id: stable
run: | run: |
# Save current branch
current=$(git branch --show)
echo ##[set-output name=originalBranch]$current
# Copy current, latest config to local # Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml cp authentik/lib/default.yml local.env.yml
cp -R .github .. cp -R .github ..
@ -108,7 +112,7 @@ jobs:
set -x set -x
git fetch git fetch
git reset --hard HEAD git reset --hard HEAD
git checkout $GITHUB_HEAD_REF git checkout ${{ steps.stable.outputs.originalBranch }}
pipenv sync --dev pipenv sync --dev
- name: prepare - name: prepare
env: env:
@ -176,7 +180,7 @@ jobs:
testspace [integration]unittest.xml --link=codecov testspace [integration]unittest.xml --link=codecov
- if: ${{ always() }} - if: ${{ always() }}
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
test-e2e: test-e2e-provider:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -215,12 +219,59 @@ jobs:
npm run build npm run build
- name: run e2e - name: run e2e
run: | run: |
pipenv run make test-e2e pipenv run make test-e2e-provider
pipenv run coverage xml pipenv run coverage xml
- name: run testspace - name: run testspace
if: ${{ always() }} if: ${{ always() }}
run: | run: |
testspace [e2e]unittest.xml --link=codecov testspace [e2e-provider]unittest.xml --link=codecov
- if: ${{ always() }}
uses: codecov/codecov-action@v2
test-e2e-rest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- id: cache-pipenv
uses: actions/cache@v2.1.7
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/docker-compose.yml up -d
- id: cache-web
uses: actions/cache@v2.1.7
with:
path: web/dist
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/**') }}
- name: prepare web ui
if: steps.cache-web.outputs.cache-hit != 'true'
run: |
cd web
npm i
npm run build
- name: run e2e
run: |
pipenv run make test-e2e-rest
pipenv run coverage xml
- name: run testspace
if: ${{ always() }}
run: |
testspace [e2e-rest]unittest.xml --link=codecov
- if: ${{ always() }} - if: ${{ always() }}
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
ci-core-mark: ci-core-mark:
@ -230,7 +281,8 @@ jobs:
- test-migrations-from-stable - test-migrations-from-stable
- test-unittest - test-unittest
- test-integration - test-integration
- test-e2e - test-e2e-rest
- test-e2e-provider
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo mark - run: echo mark
@ -252,7 +304,7 @@ jobs:
- name: prepare variables - name: prepare variables
id: ev id: ev
env: env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: | run: |
python ./scripts/gh_env.py python ./scripts/gh_env.py
- name: Login to Container Registry - name: Login to Container Registry

View File

@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '^1.16.3' go-version: "^1.17"
- name: Run linter - name: Run linter
run: | run: |
# Create folder structure for go embeds # Create folder structure for go embeds
@ -58,7 +58,7 @@ jobs:
- name: prepare variables - name: prepare variables
id: ev id: ev
env: env:
DOCKER_USERNAME: ${{ secrets.HARBOR_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: | run: |
python ./scripts/gh_env.py python ./scripts/gh_env.py
- name: Login to Container Registry - name: Login to Container Registry
@ -80,3 +80,41 @@ jobs:
build-args: | build-args: |
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
platforms: ${{ matrix.arch }} platforms: ${{ matrix.arch }}
build-outpost-binary:
timeout-minutes: 120
needs:
- ci-outpost-mark
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
type:
- proxy
- ldap
goos: [linux]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "^1.17"
- uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Build web
run: |
cd web
npm install
npm run build-proxy
- name: Build outpost
run: |
set -x
export GOOS=${{ matrix.goos }}
export GOARCH=${{ matrix.goarch }}
go build -tags=outpost_static_embed -v -o ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }} ./cmd/${{ matrix.type }}
- uses: actions/upload-artifact@v2
with:
name: authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}
path: ./authentik-outpost-${{ matrix.type }}_${{ matrix.goos }}_${{ matrix.goarch }}

View File

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

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.9.7

View File

@ -58,8 +58,6 @@ RUN apt-get update && \
curl ca-certificates gnupg git runit libpq-dev \ curl ca-certificates gnupg git runit libpq-dev \
postgresql-client build-essential libxmlsec1-dev \ postgresql-client build-essential libxmlsec1-dev \
pkg-config libmaxminddb0 && \ pkg-config libmaxminddb0 && \
pip install lxml==4.6.4 --no-cache-dir && \
export C_INCLUDE_PATH=/usr/local/lib/python3.10/site-packages/lxml/includes && \
pip install -r /requirements.txt --no-cache-dir && \ pip install -r /requirements.txt --no-cache-dir && \
apt-get remove --purge -y build-essential git && \ apt-get remove --purge -y build-essential git && \
apt-get autoremove --purge -y && \ apt-get autoremove --purge -y && \

View File

@ -4,13 +4,16 @@ UID = $(shell id -u)
GID = $(shell id -g) GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.npm_version) NPM_VERSION = $(shell python -m scripts.npm_version)
all: lint-fix lint test gen all: lint-fix lint test gen web
test-integration: test-integration:
coverage run manage.py test tests/integration coverage run manage.py test tests/integration
test-e2e: test-e2e-provider:
coverage run manage.py test tests/e2e coverage run manage.py test tests/e2e/test_provider*
test-e2e-rest:
coverage run manage.py test tests/e2e/test_flows* tests/e2e/test_source*
test: test:
coverage run manage.py test authentik coverage run manage.py test authentik

View File

@ -32,15 +32,14 @@ geoip2 = "*"
gunicorn = "*" gunicorn = "*"
kubernetes = "==v19.15.0" kubernetes = "==v19.15.0"
ldap3 = "*" ldap3 = "*"
# 4.7.0 and later remove `lxml-version.h` which is required by xmlsec lxml = "*"
lxml = "==4.6.5"
packaging = "*" packaging = "*"
psycopg2-binary = "*" psycopg2-binary = "*"
pycryptodome = "*" pycryptodome = "*"
pyjwt = "*" pyjwt = "*"
pyyaml = "*" pyyaml = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
sentry-sdk = "*" sentry-sdk = { git = 'https://github.com/beryju/sentry-python.git', ref = '379aee28b15d3b87b381317746c4efd24b3d7bc3' }
service_identity = "*" service_identity = "*"
structlog = "*" structlog = "*"
swagger-spec-validator = "*" swagger-spec-validator = "*"

367
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "6a89870496296af32dbc2f64b0832d4c20010829ada0b3c4dc27fee56b68fad9" "sha256": "dedb51159ef09fd9b00ab28022706f525c9df057ffd646e2a552784341a10538"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -109,11 +109,11 @@
}, },
"amqp": { "amqp": {
"hashes": [ "hashes": [
"sha256:4d9cb6b5d69183ba279e97382ff68a071864c25b561d206dab73499d3ed26d1c", "sha256:1e5f707424e544078ca196e72ae6a14887ce74e02bd126be54b7c03c971bef18",
"sha256:d757b78fd7d3c6bb60e3ee811b68145583643747ed3ec253329f086aa3a72a5d" "sha256:9cd81f7b023fc04bbb108718fbac674f06901b77bfcdce85b10e2a5d0ee91be5"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==5.0.7" "version": "==5.0.9"
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
@ -169,19 +169,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:76b3ee0d1dd860c9218bc864cd29f1ee986f6e1e75e8669725dd3c411039379e", "sha256:739705b28e6b2329ea3b481ba801d439c296aaf176f7850729147ba99bbf8a9a",
"sha256:c39cb6ed376ba1d4689ac8f6759a2b2d8a0b0424dbec0cd3af1558079bcf06e8" "sha256:8f08e8e94bf107c5e9866684e9aadf8d9f60abed0cfe5c1dba4e7328674a1986"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.20.23" "version": "==1.20.24"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:640b62110aa6d1c25553eceafb5bcd89aedeb84b191598d1f6492ad24374d285", "sha256:43006b4f52d7bb655319d3da0f615cdbee7762853acc1ebcb1d49f962e6b4806",
"sha256:7459766c4594f3b8877e8013f93f0dc6c6486acbeb7d9c9ae488396529cc2e84" "sha256:e78d48c50c8c013fb9b362c6202fece2fe868edfd89b51968080180bdff41617"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.23.23" "version": "==1.23.24"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -196,7 +196,7 @@
"sha256:1ef33f089e0a494e8d1b487508356f055c865b1955b125c00c991a4358543c80", "sha256:1ef33f089e0a494e8d1b487508356f055c865b1955b125c00c991a4358543c80",
"sha256:8eca49962b1bfc09c24d442aa55688be88efe5c24aeef89d3be135614b95c678" "sha256:8eca49962b1bfc09c24d442aa55688be88efe5c24aeef89d3be135614b95c678"
], ],
"markers": "python_version >= '3.7' and python_version < '4'", "markers": "python_version >= '3.7' and python_full_version < '4.0.0'",
"version": "==1.9.0" "version": "==1.9.0"
}, },
"cbor2": { "cbor2": {
@ -325,7 +325,7 @@
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
], ],
"markers": "python_version < '4' and python_full_version >= '3.6.2'", "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'",
"version": "==0.3.0" "version": "==0.3.0"
}, },
"click-plugins": { "click-plugins": {
@ -367,30 +367,29 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3",
"sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31",
"sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac",
"sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf",
"sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316",
"sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca",
"sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638",
"sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94",
"sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12",
"sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173",
"sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b",
"sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a",
"sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f",
"sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2",
"sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9",
"sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46",
"sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903",
"sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3",
"sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1",
"sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"
"sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==36.0.0" "version": "==36.0.1"
}, },
"dacite": { "dacite": {
"hashes": [ "hashes": [
@ -410,11 +409,11 @@
}, },
"deepmerge": { "deepmerge": {
"hashes": [ "hashes": [
"sha256:87166dbe9ba1a3348a45c9d4ada6778f518d41afc0b85aa017ea3041facc3f9c", "sha256:4b44779ed3d2fb791bb181fc2683423496fea428abb7af37feb23286de7f0a1a",
"sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd" "sha256:f851fff957697cb8f4580b465acf5c2d35841695306ff0abb9cb9c273ad76112"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.3.0" "version": "==1.0.1"
}, },
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
@ -470,11 +469,11 @@
}, },
"django-prometheus": { "django-prometheus": {
"hashes": [ "hashes": [
"sha256:c338d6efde1ca336e90c540b5e87afe9287d7bcc82d651a778f302b0be17a933", "sha256:240378a1307c408bd5fc85614a3a57f1ce633d4a222c9e291e2bbf325173b801",
"sha256:dd3f8da1399140fbef5c00d1526a23d1ade286b144281c325f8e409a781643f2" "sha256:e6616770d8820b8834762764bf1b76ec08e1b98e72a6f359d488a2e15fe3537c"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.1.0" "version": "==2.2.0"
}, },
"django-redis": { "django-redis": {
"hashes": [ "hashes": [
@ -494,11 +493,11 @@
}, },
"djangorestframework": { "djangorestframework": {
"hashes": [ "hashes": [
"sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf", "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee",
"sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2" "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.12.4" "version": "==3.13.1"
}, },
"djangorestframework-guardian": { "djangorestframework-guardian": {
"hashes": [ "hashes": [
@ -781,11 +780,11 @@
}, },
"jsonschema": { "jsonschema": {
"hashes": [ "hashes": [
"sha256:2a0f162822a64d95287990481b45d82f096e99721c86534f48201b64ebca6e8c", "sha256:0070ca8dd5bf47941d1e9d8bc115a3654b1138cfb8aff44f3e3527276107314f",
"sha256:390713469ae64b8a58698bb3cbc3859abe6925b565a973f87323ef21b09a27a8" "sha256:91ffbad994d766041c6003d5f8f475cceb890c30084bd0e64847ccb1c10e48bb"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==4.2.1" "version": "==4.3.1"
}, },
"kombu": { "kombu": {
"hashes": [ "hashes": [
@ -816,69 +815,69 @@
}, },
"lxml": { "lxml": {
"hashes": [ "hashes": [
"sha256:11ae552a78612620afd15625be9f1b82e3cc2e634f90d6b11709b10a100cba59", "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4",
"sha256:121fc6f71c692b49af6c963b84ab7084402624ffbe605287da362f8af0668ea3", "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f",
"sha256:124f09614f999551ac65e5b9875981ce4b66ac4b8e2ba9284572f741935df3d9", "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a",
"sha256:12ae2339d32a2b15010972e1e2467345b7bf962e155671239fba74c229564b7f", "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944",
"sha256:12d8d6fe3ddef629ac1349fa89a638b296a34b6529573f5055d1cb4e5245f73b", "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1",
"sha256:1a2a7659b8eb93c6daee350a0d844994d49245a0f6c05c747f619386fb90ba04", "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d",
"sha256:1ccbfe5d17835db906f2bab6f15b34194db1a5b07929cba3cf45a96dbfbfefc0", "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d",
"sha256:2f77556266a8fe5428b8759fbfc4bd70be1d1d9c9b25d2a414f6a0c0b0f09120", "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e",
"sha256:3534d7c468c044f6aef3c0aff541db2826986a29ea73f2ca831f5d5284d9b570", "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d",
"sha256:3884476a90d415be79adfa4e0e393048630d0d5bcd5757c4c07d8b4b00a1096b", "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a",
"sha256:3b95fb7e6f9c2f53db88f4642231fc2b8907d854e614710996a96f1f32018d5c", "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675",
"sha256:46515773570a33eae13e451c8fcf440222ef24bd3b26f40774dd0bd8b6db15b2", "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3",
"sha256:46f21f2600d001af10e847df9eb3b832e8a439f696c04891bcb8a8cedd859af9", "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55",
"sha256:473701599665d874919d05bb33b56180447b3a9da8d52d6d9799f381ce23f95c", "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60",
"sha256:4b9390bf973e3907d967b75be199cf1978ca8443183cf1e78ad80ad8be9cf242", "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d",
"sha256:4f415624cf8b065796649a5e4621773dc5c9ea574a944c76a7f8a6d3d2906b41", "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6",
"sha256:534032a5ceb34bba1da193b7d386ac575127cc39338379f39a164b10d97ade89", "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e",
"sha256:558485218ee06458643b929765ac1eb04519ca3d1e2dcc288517de864c747c33", "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5",
"sha256:57cf05466917e08f90e323f025b96f493f92c0344694f5702579ab4b7e2eb10d", "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5",
"sha256:59d77bfa3bea13caee95bc0d3f1c518b15049b97dd61ea8b3d71ce677a67f808", "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42",
"sha256:5d5254c815c186744c8f922e2ce861a2bdeabc06520b4b30b2f7d9767791ce6e", "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0",
"sha256:5ea121cb66d7e5cb396b4c3ca90471252b94e01809805cfe3e4e44be2db3a99c", "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d",
"sha256:60aeb14ff9022d2687ef98ce55f6342944c40d00916452bb90899a191802137a", "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489",
"sha256:642eb4cabd997c9b949a994f9643cd8ae00cf4ca8c5cd9c273962296fadf1c44", "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440",
"sha256:6548fc551de15f310dd0564751d9dc3d405278d45ea9b2b369ed1eccf142e1f5", "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e",
"sha256:68a851176c931e2b3de6214347b767451243eeed3bea34c172127bbb5bf6c210", "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6",
"sha256:6e84edecc3a82f90d44ddee2ee2a2630d4994b8471816e226d2b771cda7ac4ca", "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e",
"sha256:73e8614258404b2689a26cb5d002512b8bc4dfa18aca86382f68f959aee9b0c8", "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f",
"sha256:7679bb6e4d9a3978a46ab19a3560e8d2b7265ef3c88152e7fdc130d649789887", "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d",
"sha256:76b6c296e4f7a1a8a128aec42d128646897f9ae9a700ef6839cdc9b3900db9b5", "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03",
"sha256:7f00cc64b49d2ef19ddae898a3def9dd8fda9c3d27c8a174c2889ee757918e71", "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9",
"sha256:8021eeff7fabde21b9858ed058a8250ad230cede91764d598c2466b0ba70db8b", "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9",
"sha256:87f8f7df70b90fbe7b49969f07b347e3f978f8bd1046bb8ecae659921869202b", "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd",
"sha256:916d457ad84e05b7db52700bad0a15c56e0c3000dcaf1263b2fb7a56fe148996", "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6",
"sha256:925174cafb0f1179a7fd38da90302555d7445e34c9ece68019e53c946be7f542", "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4",
"sha256:9801bcd52ac9c795a7d81ea67471a42cffe532e46cfb750cd5713befc5c019c0", "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868",
"sha256:99cf827f5a783038eb313beee6533dddb8bdb086d7269c5c144c1c952d142ace", "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267",
"sha256:a21b78af7e2e13bec6bea12fc33bc05730197674f3e5402ce214d07026ccfebd", "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2",
"sha256:a52e8f317336a44836475e9c802f51c2dc38d612eaa76532cb1d17690338b63b", "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4",
"sha256:a702005e447d712375433ed0499cb6e1503fadd6c96a47f51d707b4d37b76d3c", "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24",
"sha256:a708c291900c40a7ecf23f1d2384ed0bc0604e24094dd13417c7e7f8f7a50d93", "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2",
"sha256:a7790a273225b0c46e5f859c1327f0f659896cc72eaa537d23aa3ad9ff2a1cc1", "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db",
"sha256:abcf7daa5ebcc89328326254f6dd6d566adb483d4d00178892afd386ab389de2", "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a",
"sha256:add017c5bd6b9ec3a5f09248396b6ee2ce61c5621f087eb2269c813cd8813808", "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8",
"sha256:af4139172ff0263d269abdcc641e944c9de4b5d660894a3ec7e9f9db63b56ac9", "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175",
"sha256:b4015baed99d046c760f09a4c59d234d8f398a454380c3cf0b859aba97136090", "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851",
"sha256:ba0006799f21d83c3717fe20e2707a10bbc296475155aadf4f5850f6659b96b9", "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b",
"sha256:bdb98f4c9e8a1735efddfaa995b0c96559792da15d56b76428bdfc29f77c4cdb", "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e",
"sha256:c34234a1bc9e466c104372af74d11a9f98338a3f72fae22b80485171a64e0144", "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986",
"sha256:c580c2a61d8297a6e47f4d01f066517dbb019be98032880d19ece7f337a9401d", "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f",
"sha256:ca9a40497f7e97a2a961c04fa8a6f23d790b0521350a8b455759d786b0bcb203", "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419",
"sha256:cab343b265e38d4e00649cbbad9278b734c5715f9bcbb72c85a1f99b1a58e19a", "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7",
"sha256:ce52aad32ec6e46d1a91ff8b8014a91538800dd533914bfc4a82f5018d971408", "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7",
"sha256:da07c7e7fc9a3f40446b78c54dbba8bfd5c9100dfecb21b65bfe3f57844f5e71", "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36",
"sha256:dc8a0dbb2a10ae8bb609584f5c504789f0f3d0d81840da4849102ec84289f952", "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc",
"sha256:e5b4b0d9440046ead3bd425eb2b852499241ee0cef1ae151038e4f87ede888c4", "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b",
"sha256:f33d8efb42e4fc2b31b3b4527940b25cdebb3026fb56a80c1c1c11a4271d2352", "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e",
"sha256:f6befb83bca720b71d6bd6326a3b26e9496ae6649e26585de024890fe50f49b8", "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17",
"sha256:fcc849b28f584ed1dbf277291ded5c32bb3476a37032df4a1d523b55faa5f944", "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3",
"sha256:ff44de36772b05c2eb74f2b4b6d1ae29b8f41ed5506310ce1258d44826ee38c1" "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.6.5" "version": "==4.7.1"
}, },
"maxminddb": { "maxminddb": {
"hashes": [ "hashes": [
@ -1312,12 +1311,12 @@
"version": "==0.5.0" "version": "==0.5.0"
}, },
"sentry-sdk": { "sentry-sdk": {
"git": "https://github.com/beryju/sentry-python.git",
"hashes": [ "hashes": [
"sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6",
"sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88"
], ],
"index": "pypi", "ref": "379aee28b15d3b87b381317746c4efd24b3d7bc3"
"version": "==1.5.0"
}, },
"service-identity": { "service-identity": {
"hashes": [ "hashes": [
@ -1329,11 +1328,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373", "sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c",
"sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e" "sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==59.6.0" "version": "==60.0.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -1353,11 +1352,11 @@
}, },
"structlog": { "structlog": {
"hashes": [ "hashes": [
"sha256:305a66201f9605a2e8a2595271a446f258175901c09c01e4c2c2a8ac5b68edf1", "sha256:68c4c29c003714fe86834f347cb107452847ba52414390a7ee583472bde00fc9",
"sha256:6ed8fadb27cf8362be0e606f5e79ccdd3b1e879aac65f9dc0ac3033fd013a7be" "sha256:fd7922e195262b337da85c2a91c84be94ccab1f8fd1957bd6986f6904e3761c8"
], ],
"index": "pypi", "index": "pypi",
"version": "==21.4.0" "version": "==21.5.0"
}, },
"swagger-spec-validator": { "swagger-spec-validator": {
"hashes": [ "hashes": [
@ -1455,9 +1454,7 @@
"version": "==4.1.1" "version": "==4.1.1"
}, },
"urllib3": { "urllib3": {
"extras": [ "extras": [],
"secure"
],
"hashes": [ "hashes": [
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
@ -1942,30 +1939,29 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3",
"sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31",
"sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac",
"sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf",
"sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316",
"sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca",
"sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638",
"sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94",
"sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12",
"sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173",
"sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b",
"sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a",
"sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f",
"sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2",
"sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9",
"sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46",
"sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903",
"sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3",
"sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1",
"sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"
"sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==36.0.0" "version": "==36.0.1"
}, },
"gitdb": { "gitdb": {
"hashes": [ "hashes": [
@ -2000,11 +1996,11 @@
}, },
"importlib-metadata": { "importlib-metadata": {
"hashes": [ "hashes": [
"sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", "sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6",
"sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" "sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.8.2" "version": "==4.10.0"
}, },
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
@ -2018,36 +2014,51 @@
"sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
"sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
], ],
"markers": "python_version < '4' and python_full_version >= '3.6.1'", "markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.10.1" "version": "==5.10.1"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
"sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7",
"sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a",
"sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c",
"sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc",
"sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f",
"sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09",
"sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442",
"sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e",
"sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029",
"sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61",
"sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb",
"sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0",
"sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35",
"sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42",
"sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1",
"sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad",
"sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443",
"sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd",
"sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9",
"sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148",
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38",
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55",
"sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36",
"sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a",
"sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b",
"sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44",
"sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6",
"sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69",
"sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4",
"sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84",
"sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de",
"sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28",
"sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c",
"sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1",
"sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8",
"sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b",
"sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "markers": "python_version >= '3.6'",
"version": "==1.6.0" "version": "==1.7.1"
}, },
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
@ -2332,11 +2343,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373", "sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c",
"sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e" "sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==59.6.0" "version": "==60.0.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -2387,11 +2398,11 @@
}, },
"tomli": { "tomli": {
"hashes": [ "hashes": [
"sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f",
"sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.2.2" "version": "==1.2.3"
}, },
"trio": { "trio": {
"hashes": [ "hashes": [
@ -2418,9 +2429,7 @@
"version": "==4.0.1" "version": "==4.0.1"
}, },
"urllib3": { "urllib3": {
"extras": [ "extras": [],
"secure"
],
"hashes": [ "hashes": [
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"

View File

@ -38,3 +38,23 @@ See [Development Documentation](https://goauthentik.io/developer-docs/?utm_sourc
## Security ## Security
See [SECURITY.md](SECURITY.md) See [SECURITY.md](SECURITY.md)
## Sponsors
This project is proudly sponsored by:
<p>
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=goauthentik.io">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
</a>
</p>
DigitalOcean provides development and testing resources for authentik.
<p>
<a href="https://www.netlify.com">
<img src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg" alt="Deploys by Netlify" />
</a>
</p>
Netlify hosts the [goauthentik.io](goauthentik.io) site.

View File

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

View File

@ -1,3 +1,3 @@
"""authentik""" """authentik"""
__version__ = "2021.12.1-rc4" __version__ = "2021.12.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -1,13 +1,6 @@
"""authentik administration metrics""" """authentik administration metrics"""
import time
from collections import Counter
from datetime import timedelta
from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.utils.timezone import now
from drf_spectacular.utils import extend_schema, extend_schema_field from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.shortcuts import get_objects_for_user
from rest_framework.fields import IntegerField, SerializerMethodField from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request from rest_framework.request import Request
@ -15,31 +8,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import Event, EventAction from authentik.events.models import EventAction
def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
"""Get event count by hour in the last day, fill with zeros"""
date_from = now() - timedelta(days=1)
result = (
Event.objects.filter(created__gte=date_from, **filter_kwargs)
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
.annotate(age_hours=ExtractHour("age"))
.values("age_hours")
.annotate(count=Count("pk"))
.order_by("age_hours")
)
data = Counter({int(d["age_hours"]): d["count"] for d in result})
results = []
_now = now()
for hour in range(0, -24, -1):
results.append(
{
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
"y_cord": data[hour * -1],
}
)
return results
class CoordinateSerializer(PassiveSerializer): class CoordinateSerializer(PassiveSerializer):
@ -58,12 +27,22 @@ class LoginMetricsSerializer(PassiveSerializer):
@extend_schema_field(CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _): def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours""" """Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN) user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.LOGIN)
.get_events_per_hour()
)
@extend_schema_field(CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _): def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED) user = self.context["user"]
return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.LOGIN_FAILED)
.get_events_per_hour()
)
class AdministrationMetricsViewSet(APIView): class AdministrationMetricsViewSet(APIView):
@ -75,4 +54,5 @@ class AdministrationMetricsViewSet(APIView):
def get(self, request: Request) -> Response: def get(self, request: Request) -> Response:
"""Login Metrics per 1h""" """Login Metrics per 1h"""
serializer = LoginMetricsSerializer(True) serializer = LoginMetricsSerializer(True)
serializer.context["user"] = request.user
return Response(serializer.data) return Response(serializer.data)

View File

@ -5,6 +5,7 @@ from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField from rest_framework.fields import ReadOnlyField
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
@ -15,7 +16,7 @@ from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.admin.api.metrics import CoordinateSerializer
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
@ -239,8 +240,10 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Metrics for application logins""" """Metrics for application logins"""
app = self.get_object() app = self.get_object()
return Response( return Response(
get_events_per_1h( get_objects_for_user(request.user, "authentik_events.view_event")
.filter(
action=EventAction.AUTHORIZE_APPLICATION, action=EventAction.AUTHORIZE_APPLICATION,
context__authorized_application__pk=app.pk.hex, context__authorized_application__pk=app.pk.hex,
) )
.get_events_per_hour()
) )

View File

@ -104,14 +104,14 @@ class SourceViewSet(
) )
matching_sources: list[UserSettingSerializer] = [] matching_sources: list[UserSettingSerializer] = []
for source in _all_sources: for source in _all_sources:
user_settings = source.ui_user_settings user_settings = source.ui_user_settings()
if not user_settings: if not user_settings:
continue continue
policy_engine = PolicyEngine(source, request.user, request) policy_engine = PolicyEngine(source, request.user, request)
policy_engine.build() policy_engine.build()
if not policy_engine.passing: if not policy_engine.passing:
continue continue
source_settings = source.ui_user_settings source_settings = source.ui_user_settings()
source_settings.initial_data["object_uid"] = source.slug source_settings.initial_data["object_uid"] = source.slug
if not source_settings.is_valid(): if not source_settings.is_valid():
LOGGER.warning(source_settings.errors) LOGGER.warning(source_settings.errors)

View File

@ -38,7 +38,7 @@ from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.admin.api.metrics import CoordinateSerializer
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.groups import GroupSerializer from authentik.core.api.groups import GroupSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
@ -184,19 +184,31 @@ class UserMetricsSerializer(PassiveSerializer):
def get_logins_per_1h(self, _): def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours""" """Get successful logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk) return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.LOGIN, user__pk=user.pk)
.get_events_per_hour()
)
@extend_schema_field(CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _): def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
return get_events_per_1h(action=EventAction.LOGIN_FAILED, context__username=user.username) return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.LOGIN_FAILED, context__username=user.username)
.get_events_per_hour()
)
@extend_schema_field(CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_authorizations_per_1h(self, _): def get_authorizations_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
return get_events_per_1h(action=EventAction.AUTHORIZE_APPLICATION, user__pk=user.pk) return (
get_objects_for_user(user, "authentik_events.view_event")
.filter(action=EventAction.AUTHORIZE_APPLICATION, user__pk=user.pk)
.get_events_per_hour()
)
class UsersFilter(FilterSet): class UsersFilter(FilterSet):

View File

@ -5,6 +5,7 @@ from typing import Callable
from uuid import uuid4 from uuid import uuid4
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from sentry_sdk.api import set_tag
SESSION_IMPERSONATE_USER = "authentik_impersonate_user" SESSION_IMPERSONATE_USER = "authentik_impersonate_user"
SESSION_IMPERSONATE_ORIGINAL_USER = "authentik_impersonate_original_user" SESSION_IMPERSONATE_ORIGINAL_USER = "authentik_impersonate_original_user"
@ -50,6 +51,7 @@ class RequestIDMiddleware:
"request_id": request_id, "request_id": request_id,
"host": request.get_host(), "host": request.get_host(),
} }
set_tag("authentik.request_id", request_id)
response = self.get_response(request) response = self.get_response(request)
response[RESPONSE_HEADER_ID] = request.request_id response[RESPONSE_HEADER_ID] = request.request_id
setattr(response, "ak_context", {}) setattr(response, "ak_context", {})

View File

@ -270,15 +270,21 @@ class Application(PolicyBindingModel):
"""Get launch URL if set, otherwise attempt to get launch URL based on provider.""" """Get launch URL if set, otherwise attempt to get launch URL based on provider."""
if self.meta_launch_url: if self.meta_launch_url:
return self.meta_launch_url return self.meta_launch_url
if self.provider: if provider := self.get_provider():
return self.get_provider().launch_url return provider.launch_url
return None return None
def get_provider(self) -> Optional[Provider]: def get_provider(self) -> Optional[Provider]:
"""Get casted provider instance""" """Get casted provider instance"""
if not self.provider: if not self.provider:
return None return None
# if the Application class has been cache, self.provider is set
# but doing a direct query lookup will fail.
# In that case, just return None
try:
return Provider.objects.get_subclass(pk=self.provider.pk) return Provider.objects.get_subclass(pk=self.provider.pk)
except Provider.DoesNotExist:
return None
def __str__(self): def __str__(self):
return self.name return self.name
@ -359,13 +365,11 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
"""Return component used to edit this object""" """Return component used to edit this object"""
raise NotImplementedError raise NotImplementedError
@property def ui_login_button(self, request: HttpRequest) -> Optional[UILoginButton]:
def ui_login_button(self) -> Optional[UILoginButton]:
"""If source uses a http-based flow, return UI Information about the login """If source uses a http-based flow, return UI Information about the login
button. If source doesn't use http-based flow, return None.""" button. If source doesn't use http-based flow, return None."""
return None return None
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
"""Entrypoint to integrate with User settings. Can either return None if no """Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or UserSettingSerializer.""" user settings are available, or UserSettingSerializer."""
@ -452,6 +456,14 @@ class Token(ManagedModel, ExpiringModel):
"""Handler which is called when this object is expired.""" """Handler which is called when this object is expired."""
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
if self.intent in [
TokenIntents.INTENT_RECOVERY,
TokenIntents.INTENT_VERIFICATION,
TokenIntents.INTENT_APP_PASSWORD,
]:
super().expire_action(*args, **kwargs)
return
self.key = default_token_key() self.key = default_token_key()
self.expires = default_token_duration() self.expires = default_token_duration()
self.save(*args, **kwargs) self.save(*args, **kwargs)

View File

@ -19,6 +19,7 @@
<script src="{% static 'dist/poly.js' %}" type="module"></script> <script src="{% static 'dist/poly.js' %}" type="module"></script>
{% block head %} {% block head %}
{% endblock %} {% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head> </head>
<body> <body>
{% block body %} {% block body %}

View File

@ -2,7 +2,7 @@
from time import sleep from time import sleep
from typing import Callable, Type from typing import Callable, Type
from django.test import TestCase from django.test import RequestFactory, TestCase
from django.utils.timezone import now from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
@ -30,6 +30,9 @@ class TestModels(TestCase):
def source_tester_factory(test_model: Type[Stage]) -> Callable: def source_tester_factory(test_model: Type[Stage]) -> Callable:
"""Test source""" """Test source"""
factory = RequestFactory()
request = factory.get("/")
def tester(self: TestModels): def tester(self: TestModels):
model_class = None model_class = None
if test_model._meta.abstract: if test_model._meta.abstract:
@ -38,8 +41,8 @@ def source_tester_factory(test_model: Type[Stage]) -> Callable:
model_class = test_model() model_class = test_model()
model_class.slug = "test" model_class.slug = "test"
self.assertIsNotNone(model_class.component) self.assertIsNotNone(model_class.component)
_ = model_class.ui_login_button _ = model_class.ui_login_button(request)
_ = model_class.ui_user_settings _ = model_class.ui_user_settings()
return tester return tester

View File

@ -41,7 +41,7 @@ class TestPropertyMappingAPI(APITestCase):
expr = "return True" expr = "return True"
self.assertEqual(PropertyMappingSerializer().validate_expression(expr), expr) self.assertEqual(PropertyMappingSerializer().validate_expression(expr), expr)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
print(PropertyMappingSerializer().validate_expression("/")) PropertyMappingSerializer().validate_expression("/")
def test_types(self): def test_types(self):
"""Test PropertyMappigns's types endpoint""" """Test PropertyMappigns's types endpoint"""

View File

@ -54,7 +54,9 @@ class TestTokenAPI(APITestCase):
def test_token_expire(self): def test_token_expire(self):
"""Test Token expire task""" """Test Token expire task"""
token: Token = Token.objects.create(expires=now(), user=get_anonymous_user()) token: Token = Token.objects.create(
expires=now(), user=get_anonymous_user(), intent=TokenIntents.INTENT_API
)
key = token.key key = token.key
clean_expired_models.delay().get() clean_expired_models.delay().get()
token.refresh_from_db() token.refresh_from_db()

View File

@ -11,10 +11,13 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate from cryptography.x509 import Certificate, load_pem_x509_certificate
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger
from authentik.lib.models import CreatedUpdatedModel from authentik.lib.models import CreatedUpdatedModel
from authentik.managed.models import ManagedModel from authentik.managed.models import ManagedModel
LOGGER = get_logger()
class CertificateKeyPair(ManagedModel, CreatedUpdatedModel): class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data` """CertificateKeyPair that can be used for signing or encrypting if `key_data`
@ -62,7 +65,8 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
password=None, password=None,
backend=default_backend(), backend=default_backend(),
) )
except ValueError: except ValueError as exc:
LOGGER.warning(exc)
return None return None
return self._private_key return self._private_key

View File

@ -2,6 +2,9 @@
from glob import glob from glob import glob
from pathlib import Path from pathlib import Path
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509.base import load_pem_x509_certificate
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -20,6 +23,22 @@ LOGGER = get_logger()
MANAGED_DISCOVERED = "goauthentik.io/crypto/discovered/%s" MANAGED_DISCOVERED = "goauthentik.io/crypto/discovered/%s"
def ensure_private_key_valid(body: str):
"""Attempt loading of an RSA Private key without password"""
load_pem_private_key(
str.encode("\n".join([x.strip() for x in body.split("\n")])),
password=None,
backend=default_backend(),
)
return body
def ensure_certificate_valid(body: str):
"""Attempt loading of a PEM-encoded certificate"""
load_pem_x509_certificate(body.encode("utf-8"), default_backend())
return body
@CELERY_APP.task(bind=True, base=MonitoredTask) @CELERY_APP.task(bind=True, base=MonitoredTask)
@prefill_task @prefill_task
def certificate_discovery(self: MonitoredTask): def certificate_discovery(self: MonitoredTask):
@ -42,11 +61,11 @@ def certificate_discovery(self: MonitoredTask):
with open(path, "r+", encoding="utf-8") as _file: with open(path, "r+", encoding="utf-8") as _file:
body = _file.read() body = _file.read()
if "BEGIN RSA PRIVATE KEY" in body: if "BEGIN RSA PRIVATE KEY" in body:
private_keys[cert_name] = body private_keys[cert_name] = ensure_private_key_valid(body)
else: else:
certs[cert_name] = body certs[cert_name] = ensure_certificate_valid(body)
except OSError as exc: except (OSError, ValueError) as exc:
LOGGER.warning("Failed to open file", exc=exc, file=path) LOGGER.warning("Failed to open file or invalid format", exc=exc, file=path)
discovered += 1 discovered += 1
for name, cert_data in certs.items(): for name, cert_data in certs.items():
cert = CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % name).first() cert = CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % name).first()
@ -60,7 +79,7 @@ def certificate_discovery(self: MonitoredTask):
cert.certificate_data = cert_data cert.certificate_data = cert_data
dirty = True dirty = True
if name in private_keys: if name in private_keys:
if cert.key_data == private_keys[name]: if cert.key_data != private_keys[name]:
cert.key_data = private_keys[name] cert.key_data = private_keys[name]
dirty = True dirty = True
if dirty: if dirty:

View File

@ -191,9 +191,12 @@ class TestCrypto(APITestCase):
with CONFIG.patch("cert_discovery_dir", temp_dir): with CONFIG.patch("cert_discovery_dir", temp_dir):
# pyright: reportGeneralTypeIssues=false # pyright: reportGeneralTypeIssues=false
certificate_discovery() # pylint: disable=no-value-for-parameter certificate_discovery() # pylint: disable=no-value-for-parameter
self.assertTrue( keypair: CertificateKeyPair = CertificateKeyPair.objects.filter(
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo").exists() managed=MANAGED_DISCOVERED % "foo"
) ).first()
self.assertIsNotNone(keypair)
self.assertIsNotNone(keypair.certificate)
self.assertIsNotNone(keypair.private_key)
self.assertTrue( self.assertTrue(
CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo.bar").exists() CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % "foo.bar").exists()
) )

View File

@ -1,4 +1,6 @@
"""Events API Views""" """Events API Views"""
from json import loads
import django_filters import django_filters
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from django.db.models.fields.json import KeyTextTransform from django.db.models.fields.json import KeyTextTransform
@ -12,6 +14,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.admin.api.metrics import CoordinateSerializer
from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -110,13 +113,20 @@ class EventViewSet(ModelViewSet):
@extend_schema( @extend_schema(
methods=["GET"], methods=["GET"],
responses={200: EventTopPerUserSerializer(many=True)}, responses={200: EventTopPerUserSerializer(many=True)},
filters=[],
parameters=[ parameters=[
OpenApiParameter(
"action",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter( OpenApiParameter(
"top_n", "top_n",
type=OpenApiTypes.INT, type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY, location=OpenApiParameter.QUERY,
required=False, required=False,
) ),
], ],
) )
@action(detail=False, methods=["GET"], pagination_class=None) @action(detail=False, methods=["GET"], pagination_class=None)
@ -137,6 +147,40 @@ class EventViewSet(ModelViewSet):
.order_by("-counted_events")[:top_n] .order_by("-counted_events")[:top_n]
) )
@extend_schema(
methods=["GET"],
responses={200: CoordinateSerializer(many=True)},
filters=[],
parameters=[
OpenApiParameter(
"action",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
"query",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
],
)
@action(detail=False, methods=["GET"], pagination_class=None)
def per_month(self, request: Request):
"""Get the count of events per month"""
filtered_action = request.query_params.get("action", EventAction.LOGIN)
try:
query = loads(request.query_params.get("query", "{}"))
except ValueError:
return Response(status=400)
return Response(
get_objects_for_user(request.user, "authentik_events.view_event")
.filter(action=filtered_action)
.filter(**query)
.get_events_per_day()
)
@extend_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def actions(self, request: Request) -> Response: def actions(self, request: Request) -> Response:

View File

@ -7,6 +7,7 @@ from typing import Optional, TypedDict
from geoip2.database import Reader from geoip2.database import Reader
from geoip2.errors import GeoIP2Error from geoip2.errors import GeoIP2Error
from geoip2.models import City from geoip2.models import City
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
@ -62,6 +63,10 @@ class GeoIPReader:
def city(self, ip_address: str) -> Optional[City]: def city(self, ip_address: str) -> Optional[City]:
"""Wrapper for Reader.city""" """Wrapper for Reader.city"""
with Hub.current.start_span(
op="authentik.events.geo.city",
description=ip_address,
):
if not self.enabled: if not self.enabled:
return None return None
self.__check_expired() self.__check_expired()

View File

@ -314,169 +314,10 @@ class Migration(migrations.Migration):
old_name="user_json", old_name="user_json",
new_name="user", new_name="user",
), ),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("sign_up", "Sign Up"),
("authorize_application", "Authorize Application"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.RemoveField( migrations.RemoveField(
model_name="event", model_name="event",
name="date", name="date",
), ),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_created", "Invite Created"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.CreateModel( migrations.CreateModel(
name="NotificationTransport", name="NotificationTransport",
fields=[ fields=[
@ -610,68 +451,6 @@ class Migration(migrations.Migration):
help_text="Only send notification once, for example when sending a webhook into a chat channel.", help_text="Only send notification once, for example when sending a webhook into a chat channel.",
), ),
), ),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("token_view", "Token View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.RunPython( migrations.RunPython(
code=token_view_to_secret_view, code=token_view_to_secret_view,
), ),
@ -688,76 +467,11 @@ class Migration(migrations.Migration):
migrations.RunPython( migrations.RunPython(
code=update_expires, code=update_expires,
), ),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AddField( migrations.AddField(
model_name="event", model_name="event",
name="tenant", name="tenant",
field=models.JSONField(blank=True, default=authentik.events.models.default_tenant), field=models.JSONField(blank=True, default=authentik.events.models.default_tenant),
), ),
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
migrations.AlterField( migrations.AlterField(
model_name="event", model_name="event",
name="action", name="action",
@ -776,6 +490,7 @@ class Migration(migrations.Migration):
("source_linked", "Source Linked"), ("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"), ("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"), ("impersonation_ended", "Impersonation Ended"),
("flow_execution", "Flow Execution"),
("policy_execution", "Policy Execution"), ("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"), ("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"), ("property_mapping_exception", "Property Mapping Exception"),

View File

@ -1,12 +1,20 @@
"""authentik events models""" """authentik events models"""
import time
from collections import Counter
from datetime import timedelta from datetime import timedelta
from inspect import getmodule, stack from inspect import currentframe
from smtplib import SMTPException from smtplib import SMTPException
from typing import TYPE_CHECKING, Optional, Type, Union from typing import TYPE_CHECKING, Optional, Type, Union
from uuid import uuid4 from uuid import uuid4
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.db.models.functions.datetime import ExtractDay
from django.db.models.manager import Manager
from django.db.models.query import QuerySet
from django.http import HttpRequest from django.http import HttpRequest
from django.http.request import QueryDict from django.http.request import QueryDict
from django.utils.timezone import now from django.utils.timezone import now
@ -70,6 +78,7 @@ class EventAction(models.TextChoices):
IMPERSONATION_STARTED = "impersonation_started" IMPERSONATION_STARTED = "impersonation_started"
IMPERSONATION_ENDED = "impersonation_ended" IMPERSONATION_ENDED = "impersonation_ended"
FLOW_EXECUTION = "flow_execution"
POLICY_EXECUTION = "policy_execution" POLICY_EXECUTION = "policy_execution"
POLICY_EXCEPTION = "policy_exception" POLICY_EXCEPTION = "policy_exception"
PROPERTY_MAPPING_EXCEPTION = "property_mapping_exception" PROPERTY_MAPPING_EXCEPTION = "property_mapping_exception"
@ -90,6 +99,72 @@ class EventAction(models.TextChoices):
CUSTOM_PREFIX = "custom_" CUSTOM_PREFIX = "custom_"
class EventQuerySet(QuerySet):
"""Custom events query set with helper functions"""
def get_events_per_hour(self) -> list[dict[str, int]]:
"""Get event count by hour in the last day, fill with zeros"""
date_from = now() - timedelta(days=1)
result = (
self.filter(created__gte=date_from)
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
.annotate(age_hours=ExtractHour("age"))
.values("age_hours")
.annotate(count=Count("pk"))
.order_by("age_hours")
)
data = Counter({int(d["age_hours"]): d["count"] for d in result})
results = []
_now = now()
for hour in range(0, -24, -1):
results.append(
{
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
"y_cord": data[hour * -1],
}
)
return results
def get_events_per_day(self) -> list[dict[str, int]]:
"""Get event count by hour in the last day, fill with zeros"""
date_from = now() - timedelta(weeks=4)
result = (
self.filter(created__gte=date_from)
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
.annotate(age_days=ExtractDay("age"))
.values("age_days")
.annotate(count=Count("pk"))
.order_by("age_days")
)
data = Counter({int(d["age_days"]): d["count"] for d in result})
results = []
_now = now()
for day in range(0, -30, -1):
results.append(
{
"x_cord": time.mktime((_now + timedelta(days=day)).timetuple()) * 1000,
"y_cord": data[day * -1],
}
)
return results
class EventManager(Manager):
"""Custom helper methods for Events"""
def get_queryset(self) -> QuerySet:
"""use custom queryset"""
return EventQuerySet(self.model, using=self._db)
def get_events_per_hour(self) -> list[dict[str, int]]:
"""Wrap method from queryset"""
return self.get_queryset().get_events_per_hour()
def get_events_per_day(self) -> list[dict[str, int]]:
"""Wrap method from queryset"""
return self.get_queryset().get_events_per_day()
class Event(ExpiringModel): class Event(ExpiringModel):
"""An individual Audit/Metrics/Notification/Error Event""" """An individual Audit/Metrics/Notification/Error Event"""
@ -105,6 +180,8 @@ class Event(ExpiringModel):
# Shadow the expires attribute from ExpiringModel to override the default duration # Shadow the expires attribute from ExpiringModel to override the default duration
expires = models.DateTimeField(default=default_event_duration) expires = models.DateTimeField(default=default_event_duration)
objects = EventManager()
@staticmethod @staticmethod
def _get_app_from_request(request: HttpRequest) -> str: def _get_app_from_request(request: HttpRequest) -> str:
if not isinstance(request, HttpRequest): if not isinstance(request, HttpRequest):
@ -115,14 +192,15 @@ class Event(ExpiringModel):
def new( def new(
action: Union[str, EventAction], action: Union[str, EventAction],
app: Optional[str] = None, app: Optional[str] = None,
_inspect_offset: int = 1,
**kwargs, **kwargs,
) -> "Event": ) -> "Event":
"""Create new Event instance from arguments. Instance is NOT saved.""" """Create new Event instance from arguments. Instance is NOT saved."""
if not isinstance(action, EventAction): if not isinstance(action, EventAction):
action = EventAction.CUSTOM_PREFIX + action action = EventAction.CUSTOM_PREFIX + action
if not app: if not app:
app = getmodule(stack()[_inspect_offset][0]).__name__ current = currentframe()
parent = current.f_back
app = parent.f_globals["__name__"]
cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs)) cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs))
event = Event(action=action, app=app, context=cleaned_kwargs) event = Event(action=action, app=app, context=cleaned_kwargs)
return event return event

View File

@ -46,7 +46,7 @@ class TaskResult:
def with_error(self, exc: Exception) -> "TaskResult": def with_error(self, exc: Exception) -> "TaskResult":
"""Since errors might not always be pickle-able, set the traceback""" """Since errors might not always be pickle-able, set the traceback"""
self.messages.extend(exception_to_string(exc).splitlines()) self.messages.append(str(exc))
return self return self

View File

@ -90,7 +90,7 @@ class StageViewSet(
stages += list(configurable_stage.objects.all().order_by("name")) stages += list(configurable_stage.objects.all().order_by("name"))
matching_stages: list[dict] = [] matching_stages: list[dict] = []
for stage in stages: for stage in stages:
user_settings = stage.ui_user_settings user_settings = stage.ui_user_settings()
if not user_settings: if not user_settings:
continue continue
user_settings.initial_data["object_uid"] = str(stage.pk) user_settings.initial_data["object_uid"] = str(stage.pk)

View File

@ -75,7 +75,6 @@ class Stage(SerializerModel):
"""Return component used to edit this object""" """Return component used to edit this object"""
raise NotImplementedError raise NotImplementedError
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
"""Entrypoint to integrate with User settings. Can either return None if no """Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or a challenge.""" user settings are available, or a challenge."""

View File

@ -126,7 +126,9 @@ class FlowPlanner:
) -> FlowPlan: ) -> FlowPlan:
"""Check each of the flows' policies, check policies for each stage with PolicyBinding """Check each of the flows' policies, check policies for each stage with PolicyBinding
and return ordered list""" and return ordered list"""
with Hub.current.start_span(op="flow.planner.plan", description=self.flow.slug) as span: with Hub.current.start_span(
op="authentik.flow.planner.plan", description=self.flow.slug
) as span:
span: Span span: Span
span.set_data("flow", self.flow) span.set_data("flow", self.flow)
span.set_data("request", request) span.set_data("request", request)
@ -181,7 +183,7 @@ class FlowPlanner:
"""Build flow plan by checking each stage in their respective """Build flow plan by checking each stage in their respective
order and checking the applied policies""" order and checking the applied policies"""
with Hub.current.start_span( with Hub.current.start_span(
op="flow.planner.build_plan", op="authentik.flow.planner.build_plan",
description=self.flow.slug, description=self.flow.slug,
) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time(): ) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time():
span: Span span: Span

View File

@ -6,6 +6,7 @@ from django.http.response import HttpResponse
from django.urls import reverse from django.urls import reverse
from django.views.generic.base import View from django.views.generic.base import View
from rest_framework.request import Request from rest_framework.request import Request
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import DEFAULT_AVATAR, User from authentik.core.models import DEFAULT_AVATAR, User
@ -94,7 +95,15 @@ class ChallengeStageView(StageView):
keep_context=keep_context, keep_context=keep_context,
) )
return self.executor.restart_flow(keep_context) return self.executor.restart_flow(keep_context)
with Hub.current.start_span(
op="authentik.flow.stage.challenge_invalid",
description=self.__class__.__name__,
):
return self.challenge_invalid(challenge) return self.challenge_invalid(challenge)
with Hub.current.start_span(
op="authentik.flow.stage.challenge_valid",
description=self.__class__.__name__,
):
return self.challenge_valid(challenge) return self.challenge_valid(challenge)
def format_title(self) -> str: def format_title(self) -> str:
@ -104,6 +113,10 @@ class ChallengeStageView(StageView):
} }
def _get_challenge(self, *args, **kwargs) -> Challenge: def _get_challenge(self, *args, **kwargs) -> Challenge:
with Hub.current.start_span(
op="authentik.flow.stage.get_challenge",
description=self.__class__.__name__,
):
challenge = self.get_challenge(*args, **kwargs) challenge = self.get_challenge(*args, **kwargs)
if "flow_info" not in challenge.initial_data: if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo( flow_info = ContextualFlowInfo(

View File

@ -32,7 +32,7 @@ class TestFlowsAPI(APITestCase):
def test_models(self): def test_models(self):
"""Test that ui_user_settings returns none""" """Test that ui_user_settings returns none"""
self.assertIsNone(Stage().ui_user_settings) self.assertIsNone(Stage().ui_user_settings())
def test_api_serializer(self): def test_api_serializer(self):
"""Test that stage serializer returns the correct type""" """Test that stage serializer returns the correct type"""

View File

@ -23,7 +23,7 @@ def model_tester_factory(test_model: Type[Stage]) -> Callable:
model_class = test_model() model_class = test_model()
self.assertTrue(issubclass(model_class.type, StageView)) self.assertTrue(issubclass(model_class.type, StageView))
self.assertIsNotNone(test_model.component) self.assertIsNotNone(test_model.component)
_ = model_class.ui_user_settings _ = model_class.ui_user_settings()
return tester return tester

View File

@ -160,7 +160,7 @@ class FlowExecutorView(APIView):
# pylint: disable=unused-argument, too-many-return-statements # pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
with Hub.current.start_span( with Hub.current.start_span(
op="flow.executor.dispatch", description=self.flow.slug op="authentik.flow.executor.dispatch", description=self.flow.slug
) as span: ) as span:
span.set_data("authentik Flow", self.flow.slug) span.set_data("authentik Flow", self.flow.slug)
get_params = QueryDict(request.GET.get("query", "")) get_params = QueryDict(request.GET.get("query", ""))
@ -275,7 +275,7 @@ class FlowExecutorView(APIView):
) )
try: try:
with Hub.current.start_span( with Hub.current.start_span(
op="flow.executor.stage", op="authentik.flow.executor.stage",
description=class_to_path(self.current_stage_view.__class__), description=class_to_path(self.current_stage_view.__class__),
) as span: ) as span:
span.set_data("Method", "GET") span.set_data("Method", "GET")
@ -319,7 +319,7 @@ class FlowExecutorView(APIView):
) )
try: try:
with Hub.current.start_span( with Hub.current.start_span(
op="flow.executor.stage", op="authentik.flow.executor.stage",
description=class_to_path(self.current_stage_view.__class__), description=class_to_path(self.current_stage_view.__class__),
) as span: ) as span:
span.set_data("Method", "POST") span.set_data("Method", "POST")
@ -371,6 +371,12 @@ class FlowExecutorView(APIView):
NEXT_ARG_NAME, "authentik_core:root-redirect" NEXT_ARG_NAME, "authentik_core:root-redirect"
) )
self.cancel() self.cancel()
Event.new(
action=EventAction.FLOW_EXECUTION,
flow=self.flow,
designation=self.flow.designation,
successful=True,
).from_http(self.request)
return to_stage_response(self.request, redirect_with_qs(next_param)) return to_stage_response(self.request, redirect_with_qs(next_param))
def stage_ok(self) -> HttpResponse: def stage_ok(self) -> HttpResponse:

View File

@ -106,7 +106,7 @@ class FlowInspectorView(APIView):
else: else:
try: try:
current_plan = request.session[SESSION_KEY_HISTORY][-1] current_plan = request.session[SESSION_KEY_HISTORY][-1]
except KeyError: except IndexError:
return Response(status=400) return Response(status=400)
is_completed = True is_completed = True
current_serializer = FlowInspectorPlanSerializer( current_serializer = FlowInspectorPlanSerializer(

View File

@ -64,7 +64,7 @@ outposts:
# %(type)s: Outpost type; proxy, ldap, etc # %(type)s: Outpost type; proxy, ldap, etc
# %(version)s: Current version; 2021.4.1 # %(version)s: Current version; 2021.4.1
# %(build_hash)s: Build hash if you're running a beta version # %(build_hash)s: Build hash if you're running a beta version
container_image_base: goauthentik.io/%(type)s:%(version)s container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s
cookie_domain: null cookie_domain: null
disable_update_check: false disable_update_check: false

View File

@ -80,8 +80,9 @@ class BaseEvaluator:
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised. """Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
If any exception is raised during execution, it is raised. If any exception is raised during execution, it is raised.
The result is returned without any type-checking.""" The result is returned without any type-checking."""
with Hub.current.start_span(op="lib.evaluator.evaluate") as span: with Hub.current.start_span(op="authentik.lib.evaluator.evaluate") as span:
span: Span span: Span
span.description = self._filename
span.set_data("expression", expression_source) span.set_data("expression", expression_source)
param_keys = self._context.keys() param_keys = self._context.keys()
try: try:

View File

@ -108,6 +108,9 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
"multiprocessing", "multiprocessing",
"django_redis", "django_redis",
"django.security.DisallowedHost", "django.security.DisallowedHost",
"django_redis.cache",
"celery.backends.redis",
"celery.worker",
]: ]:
return None return None
LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None)) LOGGER.debug("sending event to sentry", exc=exc_value, source_logger=event.get("logger", None))

View File

@ -116,6 +116,7 @@ class OutpostFilter(FilterSet):
"providers": ["isnull"], "providers": ["isnull"],
"name": ["iexact", "icontains"], "name": ["iexact", "icontains"],
"service_connection__name": ["iexact", "icontains"], "service_connection__name": ["iexact", "icontains"],
"managed": ["iexact", "icontains"],
} }

View File

@ -110,7 +110,7 @@ class DockerController(BaseController):
image = self.get_container_image() image = self.get_container_image()
try: try:
self.client.images.pull(image) self.client.images.pull(image)
except DockerException: except DockerException: # pragma: no cover
image = f"goauthentik.io/{self.outpost.type}:latest" image = f"goauthentik.io/{self.outpost.type}:latest"
self.client.images.pull(image) self.client.images.pull(image)
return image return image
@ -144,7 +144,7 @@ class DockerController(BaseController):
True, True,
) )
def _migrate_container_name(self): def _migrate_container_name(self): # pragma: no cover
"""Migrate 2021.9 to 2021.10+""" """Migrate 2021.9 to 2021.10+"""
old_name = f"authentik-proxy-{self.outpost.uuid.hex}" old_name = f"authentik-proxy-{self.outpost.uuid.hex}"
try: try:
@ -169,7 +169,7 @@ class DockerController(BaseController):
# Check if the container is out of date, delete it and retry # Check if the container is out of date, delete it and retry
if len(container.image.tags) > 0: if len(container.image.tags) > 0:
should_image = self.try_pull_image() should_image = self.try_pull_image()
if should_image not in container.image.tags: if should_image not in container.image.tags: # pragma: no cover
self.logger.info( self.logger.info(
"Container has mismatched image, re-creating...", "Container has mismatched image, re-creating...",
has=container.image.tags, has=container.image.tags,

View File

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

View File

@ -481,6 +481,8 @@ class OutpostState:
def for_outpost(outpost: Outpost) -> list["OutpostState"]: def for_outpost(outpost: Outpost) -> list["OutpostState"]:
"""Get all states for an outpost""" """Get all states for an outpost"""
keys = cache.keys(f"{outpost.state_cache_prefix}_*") keys = cache.keys(f"{outpost.state_cache_prefix}_*")
if not keys:
return []
states = [] states = []
for key in keys: for key in keys:
instance_uid = key.replace(f"{outpost.state_cache_prefix}_", "") instance_uid = key.replace(f"{outpost.state_cache_prefix}_", "")

View File

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

View File

@ -90,7 +90,7 @@ class PolicyEngine:
def build(self) -> "PolicyEngine": def build(self) -> "PolicyEngine":
"""Build wrapper which monitors performance""" """Build wrapper which monitors performance"""
with Hub.current.start_span( with Hub.current.start_span(
op="policy.engine.build", op="authentik.policy.engine.build",
description=self.__pbm, description=self.__pbm,
) as span, HIST_POLICIES_BUILD_TIME.labels( ) as span, HIST_POLICIES_BUILD_TIME.labels(
object_name=self.__pbm, object_name=self.__pbm,

View File

@ -66,6 +66,7 @@ class Migration(migrations.Migration):
("source_linked", "Source Linked"), ("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"), ("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"), ("impersonation_ended", "Impersonation Ended"),
("flow_execution", "Flow Execution"),
("policy_execution", "Policy Execution"), ("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"), ("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"), ("property_mapping_exception", "Property Mapping Exception"),

View File

@ -74,4 +74,4 @@ class TestExpressionPolicyAPI(APITestCase):
expr = "return True" expr = "return True"
self.assertEqual(ExpressionPolicySerializer().validate_expression(expr), expr) self.assertEqual(ExpressionPolicySerializer().validate_expression(expr), expr)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
print(ExpressionPolicySerializer().validate_expression("/")) ExpressionPolicySerializer().validate_expression("/")

View File

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

View File

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

View File

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

View File

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

View File

@ -130,7 +130,7 @@ class PolicyProcess(PROCESS_CLASS):
def profiling_wrapper(self): def profiling_wrapper(self):
"""Run with profiling enabled""" """Run with profiling enabled"""
with Hub.current.start_span( with Hub.current.start_span(
op="policy.process.execute", op="authentik.policy.process.execute",
) as span, HIST_POLICIES_EXECUTION_TIME.labels( ) as span, HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=self.binding.order, binding_order=self.binding.order,
binding_target_type=self.binding.target_type, binding_target_type=self.binding.target_type,

View File

@ -8,7 +8,6 @@ from datetime import datetime
from hashlib import sha256 from hashlib import sha256
from typing import Any, Optional, Type from typing import Any, Optional, Type
from urllib.parse import urlparse from urllib.parse import urlparse
from uuid import uuid4
from dacite import from_dict from dacite import from_dict
from django.db import models from django.db import models
@ -225,7 +224,7 @@ class OAuth2Provider(Provider):
token = RefreshToken( token = RefreshToken(
user=user, user=user,
provider=self, provider=self,
refresh_token=uuid4().hex, refresh_token=generate_key(),
expires=timezone.now() + timedelta_from_string(self.token_validity), expires=timezone.now() + timedelta_from_string(self.token_validity),
scope=scope, scope=scope,
) )
@ -434,7 +433,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
"""Create access token with a similar format as Okta, Keycloak, ADFS""" """Create access token with a similar format as Okta, Keycloak, ADFS"""
token = self.create_id_token(user, request).to_dict() token = self.create_id_token(user, request).to_dict()
token["cid"] = self.provider.client_id token["cid"] = self.provider.client_id
token["uid"] = uuid4().hex token["uid"] = generate_key()
return self.provider.encode(token) return self.provider.encode(token)
def create_id_token(self, user: User, request: HttpRequest) -> IDToken: def create_id_token(self, user: User, request: HttpRequest) -> IDToken:

View File

@ -95,6 +95,12 @@ class TokenParams:
self.refresh_token = RefreshToken.objects.get( self.refresh_token = RefreshToken.objects.get(
refresh_token=raw_token, provider=self.provider refresh_token=raw_token, provider=self.provider
) )
if self.refresh_token.is_expired:
LOGGER.warning(
"Refresh token is expired",
token=raw_token,
)
raise TokenError("invalid_grant")
# https://tools.ietf.org/html/rfc6749#section-6 # https://tools.ietf.org/html/rfc6749#section-6
# Fallback to original token's scopes when none are given # Fallback to original token's scopes when none are given
if not self.scope: if not self.scope:
@ -138,6 +144,12 @@ class TokenParams:
try: try:
self.authorization_code = AuthorizationCode.objects.get(code=raw_code) self.authorization_code = AuthorizationCode.objects.get(code=raw_code)
if self.authorization_code.is_expired:
LOGGER.warning(
"Code is expired",
token=raw_code,
)
raise TokenError("invalid_grant")
except AuthorizationCode.DoesNotExist: except AuthorizationCode.DoesNotExist:
LOGGER.warning("Code does not exist", code=raw_code) LOGGER.warning("Code does not exist", code=raw_code)
raise TokenError("invalid_grant") raise TokenError("invalid_grant")
@ -194,8 +206,10 @@ class TokenView(View):
self.params = TokenParams.parse(request, self.provider, client_id, client_secret) self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE: if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
LOGGER.info("Converting authorization code to refresh token")
return TokenResponse(self.create_code_response()) return TokenResponse(self.create_code_response())
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN: if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
LOGGER.info("Refreshing refresh token")
return TokenResponse(self.create_refresh_response()) return TokenResponse(self.create_refresh_response())
raise ValueError(f"Invalid grant_type: {self.params.grant_type}") raise ValueError(f"Invalid grant_type: {self.params.grant_type}")
except TokenError as error: except TokenError as error:

View File

@ -89,6 +89,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
# goes to the same pod # goes to the same pod
"nginx.ingress.kubernetes.io/affinity": "cookie", "nginx.ingress.kubernetes.io/affinity": "cookie",
"traefik.ingress.kubernetes.io/affinity": "true", "traefik.ingress.kubernetes.io/affinity": "true",
# Buffer sizes for large headers with JWTs
"nginx.ingress.kubernetes.io/proxy-buffers-number": "4", "nginx.ingress.kubernetes.io/proxy-buffers-number": "4",
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k", "nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
} }

View File

@ -96,6 +96,16 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
super().reconcile(current, reference) super().reconcile(current, reference)
if current.spec.forwardAuth.address != reference.spec.forwardAuth.address: if current.spec.forwardAuth.address != reference.spec.forwardAuth.address:
raise NeedsUpdate() raise NeedsUpdate()
if (
current.spec.forwardAuth.authResponseHeadersRegex
!= reference.spec.forwardAuth.authResponseHeadersRegex
):
raise NeedsUpdate()
# Ensure all of our headers are set, others can be added by the user.
if not set(current.spec.forwardAuth.authResponseHeaders).issubset(
reference.spec.forwardAuth.authResponseHeaders
):
raise NeedsUpdate()
def get_reference_object(self) -> TraefikMiddleware: def get_reference_object(self) -> TraefikMiddleware:
"""Get deployment object for outpost""" """Get deployment object for outpost"""
@ -110,8 +120,27 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
spec=TraefikMiddlewareSpec( spec=TraefikMiddlewareSpec(
forwardAuth=TraefikMiddlewareSpecForwardAuth( forwardAuth=TraefikMiddlewareSpecForwardAuth(
address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik", address=f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik",
authResponseHeaders=[], authResponseHeaders=[
authResponseHeadersRegex="^.*$", # 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",
"X-authentik-jwt",
"X-authentik-meta-jwks",
"X-authentik-meta-outpost",
"X-authentik-meta-provider",
"X-authentik-meta-app",
"X-authentik-meta-version",
],
authResponseHeadersRegex="",
trustForwardHeader=True, trustForwardHeader=True,
) )
), ),

View File

@ -21,7 +21,7 @@ class Migration(migrations.Migration):
name="audience", name="audience",
field=models.TextField( field=models.TextField(
default="", default="",
help_text="Value of the audience restriction field of the asseration.", help_text="Value of the audience restriction field of the assertion.",
), ),
), ),
migrations.AlterField( migrations.AlterField(

View File

@ -16,7 +16,7 @@ class Migration(migrations.Migration):
field=models.TextField( field=models.TextField(
blank=True, blank=True,
default="", default="",
help_text="Value of the audience restriction field of the asseration. When left empty, no audience restriction will be added.", help_text="Value of the audience restriction field of the assertion. When left empty, no audience restriction will be added.",
), ),
), ),
] ]

View File

@ -41,7 +41,7 @@ class SAMLProvider(Provider):
blank=True, blank=True,
help_text=_( help_text=_(
( (
"Value of the audience restriction field of the asseration. When left empty, " "Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added." "no audience restriction will be added."
) )
), ),

View File

@ -70,13 +70,14 @@ class AssertionProcessor:
"""Get AttributeStatement Element with Attributes from Property Mappings.""" """Get AttributeStatement Element with Attributes from Property Mappings."""
# https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions # https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
attribute_statement = Element(f"{{{NS_SAML_ASSERTION}}}AttributeStatement") attribute_statement = Element(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
user = self.http_request.user
for mapping in self.provider.property_mappings.all().select_subclasses(): for mapping in self.provider.property_mappings.all().select_subclasses():
if not isinstance(mapping, SAMLPropertyMapping): if not isinstance(mapping, SAMLPropertyMapping):
continue continue
try: try:
mapping: SAMLPropertyMapping mapping: SAMLPropertyMapping
value = mapping.evaluate( value = mapping.evaluate(
user=self.http_request.user, user=user,
request=self.http_request, request=self.http_request,
provider=self.provider, provider=self.provider,
) )

View File

@ -28,6 +28,7 @@ from sentry_sdk.integrations.boto3 import Boto3Integration
from sentry_sdk.integrations.celery import CeleryIntegration from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis import RedisIntegration
from sentry_sdk.integrations.threading import ThreadingIntegration
from authentik import ENV_GIT_HASH_KEY, __version__ from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.middleware import structlog_add_request_id from authentik.core.middleware import structlog_add_request_id
@ -66,7 +67,7 @@ SECRET_KEY = CONFIG.y("secret_key")
INTERNAL_IPS = ["127.0.0.1"] INTERNAL_IPS = ["127.0.0.1"]
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_CROSS_ORIGIN_OPENER_POLICY = None
LOGIN_URL = "authentik_flows:default-authentication" LOGIN_URL = "authentik_flows:default-authentication"
# Custom user model # Custom user model
@ -219,15 +220,16 @@ REDIS_CELERY_TLS_REQUIREMENTS = ""
if CONFIG.y_bool("redis.tls", False): if CONFIG.y_bool("redis.tls", False):
REDIS_PROTOCOL_PREFIX = "rediss://" REDIS_PROTOCOL_PREFIX = "rediss://"
REDIS_CELERY_TLS_REQUIREMENTS = f"?ssl_cert_reqs={CONFIG.y('redis.tls_reqs')}" REDIS_CELERY_TLS_REQUIREMENTS = f"?ssl_cert_reqs={CONFIG.y('redis.tls_reqs')}"
_redis_url = (
f"{REDIS_PROTOCOL_PREFIX}:"
f"{quote(CONFIG.y('redis.password'))}@{quote(CONFIG.y('redis.host'))}:"
f"{int(CONFIG.y('redis.port'))}"
)
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django_redis.cache.RedisCache", "BACKEND": "django_redis.cache.RedisCache",
"LOCATION": ( "LOCATION": f"{_redis_url}/{CONFIG.y('redis.cache_db')}",
f"{REDIS_PROTOCOL_PREFIX}:"
f"{quote(CONFIG.y('redis.password'))}@{quote(CONFIG.y('redis.host'))}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.cache_db')}"
),
"TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)), "TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
} }
@ -286,11 +288,7 @@ CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "channels_redis.core.RedisChannelLayer", "BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": { "CONFIG": {
"hosts": [ "hosts": [f"{_redis_url}/{CONFIG.y('redis.ws_db')}"],
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.ws_db')}"
],
}, },
}, },
} }
@ -344,8 +342,6 @@ TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True
LOCALE_PATHS = ["./locale"] LOCALE_PATHS = ["./locale"]
@ -368,16 +364,10 @@ CELERY_BEAT_SCHEDULE = {
CELERY_TASK_CREATE_MISSING_QUEUES = True CELERY_TASK_CREATE_MISSING_QUEUES = True
CELERY_TASK_DEFAULT_QUEUE = "authentik" CELERY_TASK_DEFAULT_QUEUE = "authentik"
CELERY_BROKER_URL = ( CELERY_BROKER_URL = (
f"{REDIS_PROTOCOL_PREFIX}:" f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_CELERY_TLS_REQUIREMENTS}"
) )
CELERY_RESULT_BACKEND = ( CELERY_RESULT_BACKEND = (
f"{REDIS_PROTOCOL_PREFIX}:" f"{_redis_url}/{CONFIG.y('redis.message_queue_db')}{REDIS_CELERY_TLS_REQUIREMENTS}"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_CELERY_TLS_REQUIREMENTS}"
) )
# Database backup # Database backup
@ -424,6 +414,7 @@ if _ERROR_REPORTING:
CeleryIntegration(), CeleryIntegration(),
RedisIntegration(), RedisIntegration(),
Boto3Integration(), Boto3Integration(),
ThreadingIntegration(propagate_hub=True),
], ],
before_send=before_send, before_send=before_send,
release=f"authentik@{__version__}", release=f"authentik@{__version__}",
@ -470,6 +461,11 @@ TEST = False
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner" TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
# We can't check TEST here as its set later by the test runner # We can't check TEST here as its set later by the test runner
LOG_LEVEL = CONFIG.y("log_level").upper() if "TF_BUILD" not in os.environ else "DEBUG" LOG_LEVEL = CONFIG.y("log_level").upper() if "TF_BUILD" not in os.environ else "DEBUG"
# We could add a custom level to stdlib logging and structlog, but it's not easy or clean
# https://stackoverflow.com/questions/54505487/custom-log-level-not-working-with-structlog
# Additionally, the entire code uses debug as highest level so that would have to be re-written too
if LOG_LEVEL == "TRACE":
LOG_LEVEL = "DEBUG"
structlog.configure_once( structlog.configure_once(
processors=[ processors=[

View File

@ -1,7 +1,6 @@
"""Source API Views""" """Source API Views"""
from typing import Any from typing import Any
from django.utils.text import slugify
from django_filters.filters import AllValuesMultipleFilter from django_filters.filters import AllValuesMultipleFilter
from django_filters.filterset import FilterSet from django_filters.filterset import FilterSet
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
@ -110,7 +109,8 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
GroupLDAPSynchronizer, GroupLDAPSynchronizer,
MembershipLDAPSynchronizer, MembershipLDAPSynchronizer,
]: ]:
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}-{sync_class.__name__}") sync_name = sync_class.__name__.replace("LDAPSynchronizer", "").lower()
task = TaskInfo.by_name(f"ldap_sync_{source.slug}_{sync_name}")
if task: if task:
results.append(task) results.append(task)
return Response(TaskSerializer(results, many=True).data) return Response(TaskSerializer(results, many=True).data)

View File

@ -29,7 +29,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn")))) group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn"))))
if self._source.object_uniqueness_field not in attributes: if self._source.object_uniqueness_field not in attributes:
self.message( self.message(
f"Cannot find uniqueness field in attributes: '{group_dn}", f"Cannot find uniqueness field in attributes: '{group_dn}'",
attributes=attributes.keys(), attributes=attributes.keys(),
dn=group_dn, dn=group_dn,
) )

View File

@ -31,7 +31,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
user_dn = self._flatten(user.get("entryDN", user.get("dn"))) user_dn = self._flatten(user.get("entryDN", user.get("dn")))
if self._source.object_uniqueness_field not in attributes: if self._source.object_uniqueness_field not in attributes:
self.message( self.message(
f"Cannot find uniqueness field in attributes: '{user_dn}", f"Cannot find uniqueness field in attributes: '{user_dn}'",
attributes=attributes.keys(), attributes=attributes.keys(),
dn=user_dn, dn=user_dn,
) )

View File

@ -1,5 +1,4 @@
"""LDAP Sync tasks""" """LDAP Sync tasks"""
from django.utils.text import slugify
from ldap3.core.exceptions import LDAPException from ldap3.core.exceptions import LDAPException
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -39,7 +38,7 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
# to set the state with # to set the state with
return return
sync = path_to_class(sync_class) sync = path_to_class(sync_class)
self.set_uid(f"{slugify(source.name)}_{sync.__name__.replace('LDAPSynchronizer', '').lower()}") self.set_uid(f"{source.slug}_{sync.__name__.replace('LDAPSynchronizer', '').lower()}")
try: try:
sync_inst = sync(source) sync_inst = sync(source)
count = sync_inst.sync() count = sync_inst.sync()

View File

@ -16,7 +16,6 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer):
model = UserOAuthSourceConnection model = UserOAuthSourceConnection
fields = ["pk", "user", "source", "identifier", "access_token"] fields = ["pk", "user", "source", "identifier", "access_token"]
extra_kwargs = { extra_kwargs = {
"user": {"read_only": True},
"access_token": {"write_only": True}, "access_token": {"write_only": True},
} }

View File

@ -14,6 +14,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.github", "authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.google", "authentik.sources.oauth.types.google",
"authentik.sources.oauth.types.oidc", "authentik.sources.oauth.types.oidc",
"authentik.sources.oauth.types.okta",
"authentik.sources.oauth.types.reddit", "authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter", "authentik.sources.oauth.types.twitter",
] ]

View File

@ -2,13 +2,13 @@
from typing import TYPE_CHECKING, Optional, Type from typing import TYPE_CHECKING, Optional, Type
from django.db import models from django.db import models
from django.http.request import HttpRequest
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from authentik.core.models import Source, UserSourceConnection from authentik.core.models import Source, UserSourceConnection
from authentik.core.types import UILoginButton, UserSettingSerializer from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.flows.challenge import ChallengeTypes, RedirectChallenge
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.sources.oauth.types.manager import SourceType from authentik.sources.oauth.types.manager import SourceType
@ -64,24 +64,15 @@ class OAuthSource(Source):
return OAuthSourceSerializer return OAuthSourceSerializer
@property def ui_login_button(self, request: HttpRequest) -> UILoginButton:
def ui_login_button(self) -> UILoginButton:
provider_type = self.type provider_type = self.type
provider = provider_type()
return UILoginButton( return UILoginButton(
challenge=RedirectChallenge(
instance={
"type": ChallengeTypes.REDIRECT.value,
"to": reverse(
"authentik_sources_oauth:oauth-client-login",
kwargs={"source_slug": self.slug},
),
}
),
icon_url=provider_type().icon_url(),
name=self.name, name=self.name,
icon_url=provider.icon_url(),
challenge=provider.login_challenge(self, request),
) )
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={
@ -183,6 +174,16 @@ class AppleOAuthSource(OAuthSource):
verbose_name_plural = _("Apple OAuth Sources") verbose_name_plural = _("Apple OAuth Sources")
class OktaOAuthSource(OAuthSource):
"""Login using a okta.com."""
class Meta:
abstract = True
verbose_name = _("Okta OAuth Source")
verbose_name_plural = _("Okta OAuth Sources")
class UserOAuthSourceConnection(UserSourceConnection): class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider.""" """Authorized remote OAuth provider."""

View File

@ -2,10 +2,15 @@
from time import time from time import time
from typing import Any, Optional from typing import Any, Optional
from django.http.request import HttpRequest
from django.urls.base import reverse
from jwt import decode, encode from jwt import decode, encode
from rest_framework.fields import CharField
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.sources.oauth.clients.oauth2 import OAuth2Client from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.manager import MANAGER, SourceType from authentik.sources.oauth.types.manager import MANAGER, SourceType
from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect from authentik.sources.oauth.views.redirect import OAuthRedirect
@ -13,18 +18,34 @@ from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger() LOGGER = get_logger()
class AppleLoginChallenge(Challenge):
"""Special challenge for apple-native authentication flow, which happens on the client."""
client_id = CharField()
component = CharField(default="ak-flow-sources-oauth-apple")
scope = CharField()
redirect_uri = CharField()
state = CharField()
class AppleChallengeResponse(ChallengeResponse):
"""Pseudo class for plex response"""
component = CharField(default="ak-flow-sources-oauth-apple")
class AppleOAuthClient(OAuth2Client): class AppleOAuthClient(OAuth2Client):
"""Apple OAuth2 client""" """Apple OAuth2 client"""
def get_client_id(self) -> str: def get_client_id(self) -> str:
parts = self.source.consumer_key.split(";") parts: list[str] = self.source.consumer_key.split(";")
if len(parts) < 3: if len(parts) < 3:
return self.source.consumer_key return self.source.consumer_key
return parts[0] return parts[0].strip()
def get_client_secret(self) -> str: def get_client_secret(self) -> str:
now = time() now = time()
parts = self.source.consumer_key.split(";") parts: list[str] = self.source.consumer_key.split(";")
if len(parts) < 3: if len(parts) < 3:
raise ValueError( raise ValueError(
( (
@ -34,14 +55,14 @@ class AppleOAuthClient(OAuth2Client):
) )
LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2]) LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2])
payload = { payload = {
"iss": parts[1], "iss": parts[1].strip(),
"iat": now, "iat": now,
"exp": now + 86400 * 180, "exp": now + 86400 * 180,
"aud": "https://appleid.apple.com", "aud": "https://appleid.apple.com",
"sub": parts[0], "sub": parts[0].strip(),
} }
# pyright: reportGeneralTypeIssues=false # pyright: reportGeneralTypeIssues=false
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2]}) jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2].strip()})
LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt) LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt)
return jwt return jwt
@ -55,7 +76,7 @@ class AppleOAuthRedirect(OAuthRedirect):
client_class = AppleOAuthClient client_class = AppleOAuthClient
def get_additional_parameters(self, source): # pragma: no cover def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
return { return {
"scope": "name email", "scope": "name email",
"response_mode": "form_post", "response_mode": "form_post",
@ -74,7 +95,6 @@ class AppleOAuth2Callback(OAuthCallback):
self, self,
info: dict[str, Any], info: dict[str, Any],
) -> dict[str, Any]: ) -> dict[str, Any]:
print(info)
return { return {
"email": info.get("email"), "email": info.get("email"),
"name": info.get("name"), "name": info.get("name"),
@ -96,3 +116,24 @@ class AppleType(SourceType):
def icon_url(self) -> str: def icon_url(self) -> str:
return "https://appleid.cdn-apple.com/appleid/button/logo" return "https://appleid.cdn-apple.com/appleid/button/logo"
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
"""Pre-general all the things required for the JS SDK"""
apple_client = AppleOAuthClient(
source,
request,
callback=reverse(
"authentik_sources_oauth:oauth-client-callback",
kwargs={"source_slug": source.slug},
),
)
args = apple_client.get_redirect_args()
return AppleLoginChallenge(
instance={
"client_id": apple_client.get_client_id(),
"scope": "name email",
"redirect_uri": args["redirect_uri"],
"state": args["state"],
"type": ChallengeTypes.NATIVE.value,
}
)

View File

@ -2,9 +2,13 @@
from enum import Enum from enum import Enum
from typing import Callable, Optional, Type from typing import Callable, Optional, Type
from django.http.request import HttpRequest
from django.templatetags.static import static from django.templatetags.static import static
from django.urls.base import reverse
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.flows.challenge import Challenge, ChallengeTypes, RedirectChallenge
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect from authentik.sources.oauth.views.redirect import OAuthRedirect
@ -37,6 +41,19 @@ class SourceType:
"""Get Icon URL for login""" """Get Icon URL for login"""
return static(f"authentik/sources/{self.slug}.svg") return static(f"authentik/sources/{self.slug}.svg")
# pylint: disable=unused-argument
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
"""Allow types to return custom challenges"""
return RedirectChallenge(
instance={
"type": ChallengeTypes.REDIRECT.value,
"to": reverse(
"authentik_sources_oauth:oauth-client-login",
kwargs={"source_slug": source.slug},
),
}
)
class SourceTypeManager: class SourceTypeManager:
"""Manager to hold all Source types.""" """Manager to hold all Source types."""

View File

@ -0,0 +1,51 @@
"""Okta OAuth Views"""
from typing import Any
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.azure_ad import AzureADClient
from authentik.sources.oauth.types.manager import MANAGER, SourceType
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
class OktaOAuthRedirect(OAuthRedirect):
"""Okta OAuth2 Redirect"""
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
return {
"scope": "openid email profile",
}
class OktaOAuth2Callback(OAuthCallback):
"""Okta OAuth2 Callback"""
# Okta has the same quirk as azure and throws an error if the access token
# is set via query parameter, so we re-use the azure client
# see https://github.com/goauthentik/authentik/issues/1910
client_class = AzureADClient
def get_user_id(self, info: dict[str, str]) -> str:
return info.get("sub", "")
def get_user_enroll_context(
self,
info: dict[str, Any],
) -> dict[str, Any]:
return {
"username": info.get("nickname"),
"email": info.get("email"),
"name": info.get("name"),
}
@MANAGER.type()
class OktaType(SourceType):
"""Okta Type definition"""
callback_view = OktaOAuth2Callback
redirect_view = OktaOAuthRedirect
name = "Okta"
slug = "okta"
urls_customizable = True

View File

@ -3,6 +3,7 @@ from typing import Optional
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.http.request import HttpRequest
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.fields import CharField from rest_framework.fields import CharField
@ -62,8 +63,7 @@ class PlexSource(Source):
return PlexSourceSerializer return PlexSourceSerializer
@property def ui_login_button(self, request: HttpRequest) -> UILoginButton:
def ui_login_button(self) -> UILoginButton:
return UILoginButton( return UILoginButton(
challenge=PlexAuthenticationChallenge( challenge=PlexAuthenticationChallenge(
{ {
@ -77,7 +77,6 @@ class PlexSource(Source):
name=self.name, name=self.name,
) )
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -167,8 +167,7 @@ class SAMLSource(Source):
reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug}) reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug})
) )
@property def ui_login_button(self, request: HttpRequest) -> UILoginButton:
def ui_login_button(self) -> UILoginButton:
return UILoginButton( return UILoginButton(
challenge=RedirectChallenge( challenge=RedirectChallenge(
instance={ instance={

View File

@ -48,7 +48,6 @@ class AuthenticatorDuoStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-authenticator-duo-form" return "ak-stage-authenticator-duo-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -141,7 +141,6 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-authenticator-sms-form" return "ak-stage-authenticator-sms-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -90,6 +90,5 @@ class AuthenticatorSMSStageTests(APITestCase):
"code": int(self.client.session[SESSION_SMS_DEVICE].token), "code": int(self.client.session[SESSION_SMS_DEVICE].token),
}, },
) )
print(response.content)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
sms_send_mock.assert_not_called() sms_send_mock.assert_not_called()

View File

@ -31,7 +31,6 @@ class AuthenticatorStaticStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-authenticator-static-form" return "ak-stage-authenticator-static-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -38,7 +38,6 @@ class AuthenticatorTOTPStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-authenticator-totp-form" return "ak-stage-authenticator-totp-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -18,7 +18,7 @@ class AuthenticateWebAuthnStageSerializer(StageSerializer):
class Meta: class Meta:
model = AuthenticateWebAuthnStage model = AuthenticateWebAuthnStage
fields = StageSerializer.Meta.fields + ["configure_flow"] fields = StageSerializer.Meta.fields + ["configure_flow", "user_verification"]
class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet): class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet):

View File

@ -0,0 +1,25 @@
# Generated by Django 4.0 on 2021-12-14 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_webauthn", "0004_auto_20210304_1850"),
]
operations = [
migrations.AddField(
model_name="authenticatewebauthnstage",
name="user_verification",
field=models.TextField(
choices=[
("required", "Required"),
("preferred", "Preferred"),
("discouraged", "Discouraged"),
],
default="preferred",
),
),
]

View File

@ -15,9 +15,30 @@ from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage from authentik.flows.models import ConfigurableStage, Stage
class UserVerification(models.TextChoices):
"""The degree to which the Relying Party wishes to verify a user's identity.
Members:
`REQUIRED`: User verification must occur
`PREFERRED`: User verification would be great, but if not that's okay too
`DISCOURAGED`: User verification should not occur, but it's okay if it does
https://www.w3.org/TR/webauthn-2/#enumdef-userverificationrequirement
"""
REQUIRED = "required"
PREFERRED = "preferred"
DISCOURAGED = "discouraged"
class AuthenticateWebAuthnStage(ConfigurableStage, Stage): class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
"""WebAuthn stage""" """WebAuthn stage"""
user_verification = models.TextField(
choices=UserVerification.choices,
default=UserVerification.PREFERRED,
)
@property @property
def serializer(self) -> BaseSerializer: def serializer(self) -> BaseSerializer:
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageSerializer from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageSerializer
@ -34,7 +55,6 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-authenticator-webauthn-form" return "ak-stage-authenticator-webauthn-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer( return UserSettingSerializer(
data={ data={

View File

@ -14,7 +14,6 @@ from webauthn.helpers.structs import (
PublicKeyCredentialCreationOptions, PublicKeyCredentialCreationOptions,
RegistrationCredential, RegistrationCredential,
ResidentKeyRequirement, ResidentKeyRequirement,
UserVerificationRequirement,
) )
from webauthn.registration.verify_registration_response import VerifiedRegistration from webauthn.registration.verify_registration_response import VerifiedRegistration
@ -27,7 +26,7 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage, WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
LOGGER = get_logger() LOGGER = get_logger()
@ -83,7 +82,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:
# clear session variables prior to starting a new registration # clear session variables prior to starting a new registration
self.request.session.pop("challenge", None) self.request.session.pop("challenge", None)
stage: AuthenticateWebAuthnStage = self.executor.current_stage
user = self.get_pending_user() user = self.get_pending_user()
registration_options: PublicKeyCredentialCreationOptions = generate_registration_options( registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
@ -94,10 +93,9 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
user_display_name=user.name, user_display_name=user.name,
authenticator_selection=AuthenticatorSelectionCriteria( authenticator_selection=AuthenticatorSelectionCriteria(
resident_key=ResidentKeyRequirement.PREFERRED, resident_key=ResidentKeyRequirement.PREFERRED,
user_verification=UserVerificationRequirement.PREFERRED, user_verification=str(stage.user_verification),
), ),
) )
registration_options.user.id = user.uid
self.request.session["challenge"] = registration_options.challenge self.request.session["challenge"] = registration_options.challenge
return AuthenticatorWebAuthnChallenge( return AuthenticatorWebAuthnChallenge(

View File

@ -37,7 +37,7 @@ def get_template_choices():
dirs = [Path(x) for x in settings.TEMPLATES[0]["DIRS"]] dirs = [Path(x) for x in settings.TEMPLATES[0]["DIRS"]]
for template_dir in dirs: for template_dir in dirs:
if not template_dir.exists(): if not template_dir.exists() or not template_dir.is_dir():
continue continue
for template in template_dir.glob("**/*.html"): for template in template_dir.glob("**/*.html"):
path = str(template) path = str(template)

View File

@ -29,4 +29,4 @@ class TestEmailStageAPI(APITestCase):
EmailTemplates.ACCOUNT_CONFIRM, EmailTemplates.ACCOUNT_CONFIRM,
) )
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
print(EmailStageSerializer().validate_template("foobar")) EmailStageSerializer().validate_template("foobar")

View File

@ -13,7 +13,7 @@ from authentik.stages.email.models import get_template_choices
def get_templates_setting(temp_dir: str) -> dict[str, Any]: def get_templates_setting(temp_dir: str) -> dict[str, Any]:
"""Patch settings TEMPLATE's dir property""" """Patch settings TEMPLATE's dir property"""
templates_setting = settings.TEMPLATES templates_setting = settings.TEMPLATES
templates_setting[0]["DIRS"] = [temp_dir] templates_setting[0]["DIRS"] = [temp_dir, "foo"]
return templates_setting return templates_setting

View File

@ -12,6 +12,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
from rest_framework.fields import BooleanField, CharField, DictField, ListField from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
@ -25,6 +26,7 @@ from authentik.flows.challenge import (
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
from authentik.sources.oauth.types.apple import AppleLoginChallenge
from authentik.sources.plex.models import PlexAuthenticationChallenge from authentik.sources.plex.models import PlexAuthenticationChallenge
from authentik.stages.identification.models import IdentificationStage from authentik.stages.identification.models import IdentificationStage
from authentik.stages.identification.signals import identification_failed from authentik.stages.identification.signals import identification_failed
@ -39,6 +41,7 @@ LOGGER = get_logger()
serializers={ serializers={
RedirectChallenge().fields["component"].default: RedirectChallenge, RedirectChallenge().fields["component"].default: RedirectChallenge,
PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge, PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge,
AppleLoginChallenge().fields["component"].default: AppleLoginChallenge,
}, },
resource_type_field_name="component", resource_type_field_name="component",
) )
@ -88,8 +91,12 @@ class IdentificationChallengeResponse(ChallengeResponse):
pre_user = self.stage.get_user(uid_field) pre_user = self.stage.get_user(uid_field)
if not pre_user: if not pre_user:
with Hub.current.start_span(
op="authentik.stages.identification.validate_invalid_wait",
description="Sleep random time on invalid user identifier",
):
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
sleep(0.30 * SystemRandom().randint(3, 7)) sleep(0.030 * SystemRandom().randint(3, 7))
LOGGER.debug("invalid_login", identifier=uid_field) LOGGER.debug("invalid_login", identifier=uid_field)
identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field) identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)
# We set the pending_user even on failure so it's part of the context, even # We set the pending_user even on failure so it's part of the context, even
@ -112,6 +119,10 @@ class IdentificationChallengeResponse(ChallengeResponse):
if not password: if not password:
LOGGER.warning("Password not set for ident+auth attempt") LOGGER.warning("Password not set for ident+auth attempt")
try: try:
with Hub.current.start_span(
op="authentik.stages.identification.authenticate",
description="User authenticate call (combo stage)",
):
user = authenticate( user = authenticate(
self.stage.request, self.stage.request,
current_stage.password_stage.backends, current_stage.password_stage.backends,
@ -191,7 +202,7 @@ class IdentificationStageView(ChallengeStageView):
current_stage.sources.filter(enabled=True).order_by("name").select_subclasses() current_stage.sources.filter(enabled=True).order_by("name").select_subclasses()
) )
for source in sources: for source in sources:
ui_login_button = source.ui_login_button ui_login_button = source.ui_login_button(self.request)
if ui_login_button: if ui_login_button:
button = asdict(ui_login_button) button = asdict(ui_login_button)
button["challenge"] = ui_login_button.challenge.data button["challenge"] = ui_login_button.challenge.data

View File

@ -5,8 +5,8 @@ from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import is_dict from authentik.core.api.utils import is_dict
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
@ -46,7 +46,7 @@ class InvitationStageViewSet(UsedByMixin, ModelViewSet):
class InvitationSerializer(ModelSerializer): class InvitationSerializer(ModelSerializer):
"""Invitation Serializer""" """Invitation Serializer"""
created_by = UserSerializer(read_only=True) created_by = GroupMemberSerializer(read_only=True)
fixed_data = JSONField(validators=[is_dict], required=False) fixed_data = JSONField(validators=[is_dict], required=False)
class Meta: class Meta:

View File

@ -63,7 +63,6 @@ class PasswordStage(ConfigurableStage, Stage):
def component(self) -> str: def component(self) -> str:
return "ak-stage-password-form" return "ak-stage-password-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]: def ui_user_settings(self) -> Optional[UserSettingSerializer]:
if not self.configure_flow: if not self.configure_flow:
return None return None

View File

@ -10,6 +10,7 @@ from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import CharField from rest_framework.fields import CharField
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import User from authentik.core.models import User
@ -43,6 +44,10 @@ def authenticate(request: HttpRequest, backends: list[str], **credentials: Any)
LOGGER.warning("Failed to import backend", path=backend_path) LOGGER.warning("Failed to import backend", path=backend_path)
continue continue
LOGGER.debug("Attempting authentication...", backend=backend_path) LOGGER.debug("Attempting authentication...", backend=backend_path)
with Hub.current.start_span(
op="authentik.stages.password.authenticate",
description=backend_path,
):
user = backend.authenticate(request, **credentials) user = backend.authenticate(request, **credentials)
if user is None: if user is None:
LOGGER.debug("Backend returned nothing, continuing", backend=backend_path) LOGGER.debug("Backend returned nothing, continuing", backend=backend_path)
@ -120,7 +125,13 @@ class PasswordStageView(ChallengeStageView):
"username": pending_user.username, "username": pending_user.username,
} }
try: try:
user = authenticate(self.request, self.executor.current_stage.backends, **auth_kwargs) with Hub.current.start_span(
op="authentik.stages.password.authenticate",
description="User authenticate call",
):
user = authenticate(
self.request, self.executor.current_stage.backends, **auth_kwargs
)
except PermissionDenied: except PermissionDenied:
del auth_kwargs["password"] del auth_kwargs["password"]
# User was found, but permission was denied (i.e. user is not active) # User was found, but permission was denied (i.e. user is not active)

View File

@ -4,6 +4,7 @@ from typing import Any
from django.db.models import F, Q from django.db.models import F, Q
from django.db.models import Value as V from django.db.models import Value as V
from django.http.request import HttpRequest from django.http.request import HttpRequest
from sentry_sdk.hub import Hub
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -28,7 +29,12 @@ def get_tenant_for_request(request: HttpRequest) -> Tenant:
def context_processor(request: HttpRequest) -> dict[str, Any]: def context_processor(request: HttpRequest) -> dict[str, Any]:
"""Context Processor that injects tenant object into every template""" """Context Processor that injects tenant object into every template"""
tenant = getattr(request, "tenant", DEFAULT_TENANT) tenant = getattr(request, "tenant", DEFAULT_TENANT)
trace = ""
span = Hub.current.scope.span
if span:
trace = span.to_traceparent()
return { return {
"tenant": tenant, "tenant": tenant,
"footer_links": CONFIG.y("footer_links"), "footer_links": CONFIG.y("footer_links"),
"sentry_trace": trace,
} }

View File

@ -21,6 +21,12 @@ Required environment variables:
func main() { func main() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "event",
log.FieldKeyTime: "timestamp",
},
})
akURL, found := os.LookupEnv("AUTHENTIK_HOST") akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found { if !found {
fmt.Println("env AUTHENTIK_HOST not set!") fmt.Println("env AUTHENTIK_HOST not set!")

View File

@ -26,6 +26,12 @@ Optionally, you can set these:
func main() { func main() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "event",
log.FieldKeyTime: "timestamp",
},
})
akURL, found := os.LookupEnv("AUTHENTIK_HOST") akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found { if !found {
fmt.Println("env AUTHENTIK_HOST not set!") fmt.Println("env AUTHENTIK_HOST not set!")

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"time" "time"
@ -20,7 +21,12 @@ var running = true
func main() { func main() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{}) log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "event",
log.FieldKeyTime: "timestamp",
},
})
l := log.WithField("logger", "authentik.root") l := log.WithField("logger", "authentik.root")
config.DefaultConfig() config.DefaultConfig()
err := config.LoadConfig("./authentik/lib/default.yml") err := config.LoadConfig("./authentik/lib/default.yml")
@ -41,9 +47,12 @@ func main() {
err := sentry.Init(sentry.ClientOptions{ err := sentry.Init(sentry.ClientOptions{
Dsn: config.G.ErrorReporting.DSN, Dsn: config.G.ErrorReporting.DSN,
AttachStacktrace: true, AttachStacktrace: true,
TracesSampleRate: 0.6, TracesSampleRate: config.G.ErrorReporting.SampleRate,
Release: fmt.Sprintf("authentik@%s", constants.VERSION), Release: fmt.Sprintf("authentik@%s", constants.VERSION),
Environment: config.G.ErrorReporting.Environment, Environment: config.G.ErrorReporting.Environment,
IgnoreErrors: []string{
http.ErrAbortHandler.Error(),
},
}) })
if err != nil { if err != nil {
l.WithError(err).Warning("failed to init sentry") l.WithError(err).Warning("failed to init sentry")
@ -69,9 +78,9 @@ func main() {
<-ex <-ex
running = false running = false
l.WithField("logger", "authentik").Info("shutting down gunicorn") l.Info("shutting down gunicorn")
go g.Kill() go g.Kill()
l.WithField("logger", "authentik").Info("shutting down webserver") l.Info("shutting down webserver")
go ws.Shutdown() go ws.Shutdown()
} }
} }
@ -89,8 +98,9 @@ func attemptStartBackend(g *gounicorn.GoUnicorn) {
func attemptProxyStart(ws *web.WebServer, u *url.URL) { func attemptProxyStart(ws *web.WebServer, u *url.URL) {
maxTries := 100 maxTries := 100
attempt := 0 attempt := 0
l := log.WithField("logger", "authentik.server")
for { for {
log.WithField("logger", "authentik").Debug("attempting to init outpost") l.Debug("attempting to init outpost")
ac := ak.NewAPIController(*u, config.G.SecretKey) ac := ak.NewAPIController(*u, config.G.SecretKey)
if ac == nil { if ac == nil {
attempt += 1 attempt += 1
@ -103,10 +113,10 @@ func attemptProxyStart(ws *web.WebServer, u *url.URL) {
srv := proxyv2.NewProxyServer(ac, 0) srv := proxyv2.NewProxyServer(ac, 0)
ws.ProxyServer = srv ws.ProxyServer = srv
ac.Server = srv ac.Server = srv
log.WithField("logger", "authentik").Debug("attempting to start outpost") l.Debug("attempting to start outpost")
err := ac.StartBackgorundTasks() err := ac.StartBackgorundTasks()
if err != nil { if err != nil {
log.WithField("logger", "authentik").WithError(err).Warning("outpost failed to start") l.WithError(err).Warning("outpost failed to start")
attempt += 1 attempt += 1
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)
if attempt > maxTries { if attempt > maxTries {

View File

@ -17,7 +17,7 @@ services:
image: redis:alpine image: redis:alpine
restart: unless-stopped restart: unless-stopped
server: server:
image: ${AUTHENTIK_IMAGE:-goauthentik.io/server}:${AUTHENTIK_TAG:-2021.12.1-rc4} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.12.3}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -38,7 +38,7 @@ services:
- "0.0.0.0:9000:9000" - "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443" - "0.0.0.0:9443:9443"
worker: worker:
image: ${AUTHENTIK_IMAGE:-goauthentik.io/server}:${AUTHENTIK_TAG:-2021.12.1-rc4} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.12.3}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
environment: environment:

2
go.mod
View File

@ -28,7 +28,7 @@ require (
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
goauthentik.io/api v0.2021104.11 goauthentik.io/api v0.2021122.2
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558

4
go.sum
View File

@ -558,8 +558,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
goauthentik.io/api v0.2021104.11 h1:LqT0LM0e/RRrxPuo6Xl5uz3PCR5ytuE+YlNlfW9w0yU= goauthentik.io/api v0.2021122.2 h1:3kvyBS7F+uxJ38qrUoWB0Rpidmnw/MHei1NNQ34daAU=
goauthentik.io/api v0.2021104.11/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE= goauthentik.io/api v0.2021122.2/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -2,6 +2,7 @@ package config
import ( import (
"io/ioutil" "io/ioutil"
"strings"
env "github.com/Netflix/go-env" env "github.com/Netflix/go-env"
"github.com/imdario/mergo" "github.com/imdario/mergo"
@ -26,6 +27,7 @@ func DefaultConfig() {
ErrorReporting: ErrorReportingConfig{ ErrorReporting: ErrorReportingConfig{
Enabled: false, Enabled: false,
DSN: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", DSN: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
SampleRate: 1,
}, },
} }
} }
@ -61,7 +63,7 @@ func FromEnv() error {
} }
func ConfigureLogger() { func ConfigureLogger() {
switch G.LogLevel { switch strings.ToLower(G.LogLevel) {
case "trace": case "trace":
log.SetLevel(log.TraceLevel) log.SetLevel(log.TraceLevel)
case "debug": case "debug":
@ -76,14 +78,14 @@ func ConfigureLogger() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }
if G.Debug { fm := log.FieldMap{
log.SetFormatter(&log.TextFormatter{})
} else {
log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "event", log.FieldKeyMsg: "event",
log.FieldKeyTime: "timestamp", log.FieldKeyTime: "timestamp",
}, }
})
if G.Debug {
log.SetFormatter(&log.TextFormatter{FieldMap: fm})
} else {
log.SetFormatter(&log.JSONFormatter{FieldMap: fm})
} }
} }

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