Compare commits

...

220 Commits

Author SHA1 Message Date
5a7508d2e0 core: fix token expiration not being updated upon key rotation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
9c31ea1aa6 core: fix expired tokens not being returned by API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
18211a2033 release: 2021.7.3 2021-08-05 19:23:03 +02:00
b4cfc56e5e web/admin: fix source form's userMatchingMode being swapped
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/sources/oauth/OAuthSourceForm.ts
#	web/src/pages/sources/plex/PlexSourceForm.ts
2021-08-05 18:48:02 +02:00
8e797fa76b outpost/ldap: fix errors with new UserSelf serializer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 18:16:06 +02:00
1b91543add core: add UserSelfSerializer and separate method for users to update themselves with limited fields
rework user settings page to better use form
closes #1227

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

# Conflicts:
#	authentik/core/api/users.py
#	web/src/elements/forms/ModelForm.ts
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/user-settings/UserSettingsPage.ts
2021-08-05 17:47:45 +02:00
1cd59be8dc web/admin: fix email being required
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/users/UserForm.ts
2021-08-05 17:46:28 +02:00
66bb68a747 lifecycle: decrease default worker count on compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 09:44:58 +02:00
aa4f7fb2b6 providers/saml: fix error when PropertyMapping return value isn't string
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-04 00:22:07 +02:00
4f1c11c5ef providers/saml: add WantAssertionsSigned
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/providers/saml/processors/metadata_parser.py
2021-08-04 00:21:54 +02:00
add7a80fdc release: 2021.7.2 2021-08-01 19:11:50 +02:00
aac91c2e9d stages/email: handle OSError
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
85e86351cd flows: fix flows not redirecting correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
d767504474 flows: don't check redirect URL when set from flow plan (set from authentik or policy)
closes #1203

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
f84cd6208c flows: fix unhandled error in stage execution not being logged as SYSTEM_EXCEPTION event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
1ec540ea9a providers/saml: fix metadata being inaccessible without authentication
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
29fe731bbf providers/saml: fix Error when getting metadata for invalid ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 14:09:22 +02:00
26e66969c9 stages/invitation: delete invite only after full enrollment flow is completed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
fe629f8b51 web/admin: fix empty column when no invitation expiry was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
72b7642c5a outposts: catch invalid ServiceConnection error in outpost controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
a97f842112 sources/plex: add background task to monitor validity of plex token
closes #1205

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
16e6e4c3b7 web/admin: add re-authenticate button for plex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1205
2021-08-01 12:33:21 +02:00
dc0d715885 web/admin: add UI to copy invitation link
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
7ecd57ecff outpost: bump timer for periodic config reloads
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
0cb4d64b57 stages/email: fix error when re-requesting email after token has expired
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
a4fd58a0db events: ensure fallback result is set for on_failure
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
fb6e8ca1eb events: remove default result for MonitoredTasks, only save when result was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 22:43:29 +02:00
4c41948e75 e2e: fix broken selenium by locking images
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:53:09 +02:00
a5c8caf909 providers/oauth2: fix error when requesting jwks keys with no rs256 aet
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:59 +02:00
970655ab21 ci: fix sentry sourcemap path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:52 +02:00
c8c7202c61 web/admin: fix LDAP Provider bind flow list being empty
closes #1192

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:54 +02:00
a3981dd3cd providers/proxy: fix hosts for ingress not being compared correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:50 +02:00
affafc31cf sources/ldap: improve ms-ad password complexity checking
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:47 +02:00
602aed674b web/admin: fully remove response cloning due to errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:44 +02:00
e6b515e3f7 release: 2021.7.1 2021-07-27 10:35:45 +02:00
36eaecfdec build(deps): bump drf-spectacular from 0.17.2 to 0.17.3 (#1188)
Bumps [drf-spectacular](https://github.com/tfranzel/drf-spectacular) from 0.17.2 to 0.17.3.
- [Release notes](https://github.com/tfranzel/drf-spectacular/releases)
- [Changelog](https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tfranzel/drf-spectacular/compare/0.17.2...0.17.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 09:27:06 +02:00
3973efae19 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1185)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.4 to 4.28.5.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.5/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 09:26:58 +02:00
d8492e0df5 build(deps): bump @typescript-eslint/parser in /web (#1186) 2021-07-27 08:47:31 +02:00
b64da0dd28 build(deps): bump boto3 from 1.18.6 to 1.18.7 (#1187) 2021-07-27 08:46:56 +02:00
c3ae3e02f3 website/docs: add go requirement
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:52:58 +02:00
7c6a96394b root: add code of conduct and PR template
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:49:17 +02:00
0fe43f8319 root: add contributing file
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 22:42:00 +02:00
7e32723748 website/docs: update terminology for dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 21:50:49 +02:00
577aa7ba79 web/admin: add status card for https and timedrift
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 19:58:26 +02:00
b752540800 core: fix pagination not working correctly with applications API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 19:12:23 +02:00
64c8ca9b5d web/admin: default to authentication flow for LDAP provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 18:47:59 +02:00
5552e0ffa7 web/admin: add notice for event_retention
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 18:47:46 +02:00
e7b7bfddd6 providers/oauth2: fix blank redirect_uri not working with TokenView
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-26 11:29:16 +02:00
28f970c795 build(deps): bump boto3 from 1.18.5 to 1.18.6 (#1183) 2021-07-26 08:40:05 +02:00
d1dbdfa9fe build(deps): bump chart.js from 3.4.1 to 3.5.0 in /web (#1182) 2021-07-26 08:39:57 +02:00
c4f4e3eac7 build(deps): bump rollup from 2.53.3 to 2.54.0 in /web (#1181) 2021-07-26 08:39:49 +02:00
f21ebf5488 core: add tests for flow_manager
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 23:20:38 +02:00
5615613ed1 core: fix CheckApplication's for_user flag not being checked correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 22:29:15 +02:00
669329e49c tenants: set tenant uuid in sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 22:28:09 +02:00
0587ab26e8 web/admin: fix ApplicationView's CheckAccess not sending UserID correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-25 21:03:32 +02:00
3c9cc9d421 Merge branch 'version-2021.7' 2021-07-24 20:07:42 +02:00
1972464a20 tenants: make event retention configurable on tenant level
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-24 20:07:12 +02:00
3041a30193 release: 2021.7.1-rc2 2021-07-24 18:32:05 +02:00
1e28a1e311 ci: fix relative path for sourcemaps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-24 17:25:37 +02:00
5a1b912b76 web: fix lint error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 22:42:45 +02:00
464c27ef17 web: improve UI for event actions
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 22:27:51 +02:00
a745022f06 website/docs: prepare 2021.7.1-rc2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 21:30:15 +02:00
0b34f70205 web/admin: fix missing dark theme for notifications
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 21:27:56 +02:00
a4b051fcc1 web: fix icon flashing in header, fix notification header icon in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 20:57:06 +02:00
5ff3e9b418 outposts/ldap: add support for member query
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 20:00:23 +02:00
8ae7403abc core: add group filter by member username and pk
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 19:35:41 +02:00
f6e1bfdfc8 outpost: fix 100% CPU Usage when not connected to websocket
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 18:57:26 +02:00
aca3a5c458 outpost: add tracing for http client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 17:37:06 +02:00
d16c24fd53 website/docs: clear up outpost uuids
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 16:07:47 +02:00
6a8be0dc71 outposts/ldap: improve parsing of LDAP filters
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-23 15:41:09 +02:00
81b9b37e5e build(deps): bump @sentry/tracing from 6.9.0 to 6.10.0 in /web (#1174)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.9.0...6.10.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  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-07-23 10:11:16 +02:00
22b01962fb build(deps): bump @sentry/tracing from 6.9.0 to 6.10.0 in /website (#1175)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.9.0...6.10.0)

---
updated-dependencies:
- dependency-name: "@sentry/tracing"
  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-07-23 10:11:05 +02:00
86cc99be35 build(deps): bump @sentry/react from 6.9.0 to 6.10.0 in /website (#1176) 2021-07-23 09:05:26 +02:00
416f917c4a build(deps): bump @sentry/browser from 6.9.0 to 6.10.0 in /web (#1177) 2021-07-23 09:05:04 +02:00
f77bece790 build(deps): bump boto3 from 1.18.4 to 1.18.5 (#1178) 2021-07-23 09:04:50 +02:00
a8dd846437 Revert "root: fix root dir for coverage"
This reverts commit 4c50769040.
2021-07-22 23:52:58 +02:00
4c50769040 root: fix root dir for coverage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 23:15:52 +02:00
34189fcc06 outposts/ldap: search users and group in parallel
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 22:55:23 +02:00
fb5c8f3d7f ci: attempt to load variable group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 22:18:58 +02:00
049a55a761 ci: add zeus
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 21:34:17 +02:00
4cd53f3d11 ci: remove unused variables
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 21:05:55 +02:00
0d0dcf8de0 outposts/ldap: optimise backend Search API requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 20:38:30 +02:00
8cd1223081 core: add email filter for user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 20:10:42 +02:00
1b4654bb1d outposts/ldap: add tracing for LDAP bind and search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 19:23:56 +02:00
0a3fade1fd providers/proxy: remove deprecated field
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 16:20:26 +02:00
ff64814f40 web/admin: improve UI for notification toggle
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 14:17:56 +02:00
cbeb6e58ac web: separate websocket connection from messages
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 13:47:27 +02:00
285a9b8b1d website/docs: remove duplicate proxy docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:48:10 +02:00
66bfa6879d outposts/proxy: add X-Auth-Groups header to pass groups
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:47:58 +02:00
c05240afbf lib: fix outpost fake-ip not working, add tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:10:25 +02:00
7370dd5f3f outposts: ensure outpost SAs always have permissions to fake IP
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 10:02:20 +02:00
477c8b099e build(deps-dev): bump pylint from 2.9.4 to 2.9.5 (#1173) 2021-07-22 09:32:24 +02:00
2c761da883 build(deps): bump boto3 from 1.18.3 to 1.18.4 (#1172) 2021-07-22 09:32:16 +02:00
75070232b1 build(deps): bump codemirror from 5.62.1 to 5.62.2 in /web (#1170) 2021-07-22 09:32:08 +02:00
690b35e1a3 build(deps): bump postcss from 8.3.5 to 8.3.6 in /website (#1169) 2021-07-22 09:31:59 +02:00
bd67f2362f build(deps): bump rollup from 2.53.2 to 2.53.3 in /web (#1171) 2021-07-22 09:31:43 +02:00
896e5adce2 sources/ldap: fix lint
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 00:40:55 +02:00
7f25b6311d web/admin: fix negative count for policies when more cached than total policies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-22 00:01:28 +02:00
253f345fc4 outposts: save certificate fingerprint and check before re-fetching to cleanup logs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:53:43 +02:00
a3abbcec6a sources/ldap: improve error handling for property mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:49:09 +02:00
70e000d327 providers/saml: improve error handling for property mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 23:14:03 +02:00
a7467e6740 providers/oauth2: handler PropertyMapping exceptions and create event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 22:51:39 +02:00
b3da94bbb8 core: broaden error catching for propertymappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 22:50:39 +02:00
e62f5a75e4 outposts: fix git hash not being set in outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 21:31:25 +02:00
39ad9d7c9d release: 2021.7.1-rc1 2021-07-21 10:44:40 +02:00
20d09c14b2 website/docs: add 2021.7
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 09:41:49 +02:00
3a4d514bae build(deps): bump @babel/core from 7.14.6 to 7.14.8 in /web (#1162)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.14.6 to 7.14.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.14.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-21 09:41:16 +02:00
4932846e14 build(deps): bump codemirror from 5.62.0 to 5.62.1 in /web (#1163)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.62.0 to 5.62.1.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.62.0...5.62.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-21 09:41:08 +02:00
bb62aa7c7f build(deps): bump actions/setup-node from 2.2.0 to 2.3.0 (#1165) 2021-07-21 09:19:25 +02:00
907b837301 build(deps): bump @babel/preset-env from 7.14.7 to 7.14.8 in /web (#1164) 2021-07-21 09:18:55 +02:00
b60a3d45dc build(deps): bump boto3 from 1.18.2 to 1.18.3 (#1166) 2021-07-21 09:18:43 +02:00
3f5585ca84 build(deps-dev): bump pylint from 2.9.3 to 2.9.4 (#1167) 2021-07-21 09:18:03 +02:00
ba9a4efc9b providers/oauth2: fix nonce field not being optional
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:34:01 +02:00
902378af53 providers/oauth2: fix redirect_uris not having blank set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:22:09 +02:00
2352a7f4d6 providers/oauth2: nonce is only required for implicit flows, don't check or fallback for other flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-21 00:21:08 +02:00
d89266a9d2 outposts/ldap: fix order of Listeners
TCP -> PROXY -> TLS

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-20 15:25:11 +02:00
d678d33756 root: add support for PROXY protocol on listeners
closes #1161

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-20 11:03:09 +02:00
49d0ccd9c7 build(deps): bump @typescript-eslint/parser in /web (#1158)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.28.3 to 4.28.4.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.4/packages/parser)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-20 09:08:16 +02:00
ea082ed9ef build(deps): bump @typescript-eslint/eslint-plugin in /web (#1159) 2021-07-20 08:33:22 +02:00
d62fc9766c build(deps): bump boto3 from 1.18.1 to 1.18.2 (#1160) 2021-07-20 08:33:12 +02:00
983747b13b website: add sentry
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 21:50:56 +02:00
de4710ea71 outpost: minor cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 17:19:48 +02:00
d55b31dd82 outposts/proxy: set server header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 17:11:11 +02:00
d87871f806 outposts/ldap: improve logging, add request ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:41:29 +02:00
148194e12b tests/e2e: add LDAPS bind tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:26:36 +02:00
a2c587be43 outposts: don't authenticate as service user for flows to set remote-ip
set outpost token as additional header and check that token (user) if they can override remote-ip

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-19 13:17:13 +02:00
673da2a96e build(deps): bump eslint from 7.30.0 to 7.31.0 in /web (#1156)
Bumps [eslint](https://github.com/eslint/eslint) from 7.30.0 to 7.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.30.0...v7.31.0)

---
updated-dependencies:
- dependency-name: eslint
  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-07-19 10:15:32 +02:00
a9a7b26264 build(deps): bump ldap3 from 2.9 to 2.9.1 (#1157)
Bumps [ldap3](https://github.com/cannatag/ldap3) from 2.9 to 2.9.1.
- [Release notes](https://github.com/cannatag/ldap3/releases)
- [Changelog](https://github.com/cannatag/ldap3/blob/dev/_changelog.txt)
- [Commits](https://github.com/cannatag/ldap3/compare/v2.9...v2.9.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 10:11:30 +02:00
83d2c442a5 tests/e2e: fix ldap tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:43:35 +02:00
4029e19b72 outposts/ldap: fix order of flow check
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:22:35 +02:00
538a466090 root: fix middleware exception for outpost
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 22:10:50 +02:00
322a343c81 root: fix log level not being set to DEBUG for tests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 21:45:08 +02:00
6ddd6bfa72 root: fix linting errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 20:54:34 +02:00
36de302250 outposts: separate CLI flow executor from ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-18 15:51:48 +02:00
9eb13c50e9 ci: fix linter for embed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 21:56:42 +02:00
cffc6a1b88 outpost/ldap: fix import
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 20:02:36 +02:00
ba437beacc build(deps): bump @rollup/plugin-replace from 2.4.2 to 3.0.0 in /web (#1152)
Bumps [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/HEAD/packages/replace) from 2.4.2 to 3.0.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Changelog](https://github.com/rollup/plugins/blob/master/packages/replace/CHANGELOG.md)
- [Commits](https://github.com/rollup/plugins/commits/wasm-v3.0.0/packages/replace)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-17 19:38:57 +02:00
da32b05eba build(deps): bump boto3 from 1.18.0 to 1.18.1 (#1154)
Bumps [boto3](https://github.com/boto/boto3) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.0...1.18.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-17 19:38:44 +02:00
45b7e7565d Merge pull request #1153 from goauthentik/dependabot/go_modules/github.com/google/uuid-1.3.0
build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0
2021-07-17 19:38:33 +02:00
a0b63f50bf outposts: fix import for self-signed cert on ldap
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 19:38:04 +02:00
dc5d571c99 root: initial merging of outpost and main project (#1030)
* root: initial merging of outpost and main project

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

* root: fix build for main server

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

* root: start deduplicating code

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

* root: add more common utils

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

* outposts: make outpost managed

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

* outposts: make managed outposts

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

* root: more code merging

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

* outposts: fix linting

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

* root: fix missing go client in dockerfile

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

* root: fix docker stage name

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

* internal: fix gunicorn not being restarted correctly

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

* internal: don't send kill signal to child as we mange it

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

* cmd: fix shutdown not being signaled properl

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 18:38:27 +02:00
05161db458 cmd: fix shutdown not being signaled properl
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 18:04:09 +02:00
311ffa9f79 internal: don't send kill signal to child as we mange it
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 17:07:35 +02:00
7cbe33d65d internal: fix gunicorn not being restarted correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 16:59:31 +02:00
be9ca48de0 root: fix docker stage name
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-17 16:40:55 +02:00
b3159a74e5 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	Dockerfile
#	internal/outpost/ak/api.go
#	internal/outpost/ak/api_uag.go
#	internal/outpost/ak/global.go
#	internal/outpost/ldap/api_tls.go
#	internal/outpost/ldap/instance_bind.go
#	internal/outpost/ldap/utils.go
#	internal/outpost/proxy/api_bundle.go
#	outpost/go.mod
#	outpost/go.sum
#	outpost/pkg/ak/cert.go
2021-07-17 12:49:38 +02:00
89fafff0af lifecycle: fix postgresql port not being passed for migrations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-16 12:04:36 +02:00
ae77c872a0 root: celery requires additional parameters when tls is enabled (#1148) 2021-07-16 08:51:09 +02:00
5f13563e03 build(deps): bump rollup from 2.53.1 to 2.53.2 in /web (#1149) 2021-07-16 08:48:48 +02:00
e17c9040bb build(deps): bump @rollup/plugin-typescript from 8.2.1 to 8.2.3 in /web (#1150) 2021-07-16 08:48:40 +02:00
280ef3d265 build(deps): bump boto3 from 1.17.112 to 1.18.0 (#1151) 2021-07-16 08:48:30 +02:00
a5bb583268 root: optional TLS support on redis connections (#1147)
* root: optional TLS support on redis connections

* root: don't use f-strings when not interpolating variables

* root: use f-string in redis protocol prefix interpolation

* root: glaring typo

* formatting

* small formatting change I missed

* root: swap around default redis protocol prefixes
2021-07-15 11:48:52 +02:00
212ff11b6d api: fix Capabilities check for s3 backup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-15 09:58:07 +02:00
1fa9d70945 build(deps): bump golang from 1.16.5 to 1.16.6 (#1144) 2021-07-15 08:39:38 +02:00
eeeaa9317b build(deps): bump golang from 1.16.5 to 1.16.6 in /outpost (#1145) 2021-07-15 08:39:26 +02:00
09b932100f build(deps): bump boto3 from 1.17.111 to 1.17.112 (#1146) 2021-07-15 08:39:17 +02:00
aa701c5725 core: don't delete expired tokens, rotate their key
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 21:47:32 +02:00
6f98833150 core: allow users to create non-expiring tokens when flag is set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 21:15:14 +02:00
30aa24ce6e outposts/ldap: more cleanup
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 20:37:27 +02:00
a426a1a0b6 outposts: cleanup UserAgent config for API Client
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 20:33:35 +02:00
061c549a40 providers/ldap: fix: dn and member fields for virtual groups (#1143)
* providers/ldap: fix: dn and member fields for virtual groups

* Refactor GetGroupDN to use string name instead to allow more flexibility
2021-07-14 14:54:55 +00:00
efa09d5e1d providers/ldap: fix: Return user DN with virtual group (#1142)
* fix: incorrect ldap virtual group member DN

Signed-off-by: Toboshii Nakama <toboshii@gmail.com>

* fix: imports

Signed-off-by: Toboshii Nakama <toboshii@gmail.com>
2021-07-14 10:59:40 +00:00
4fe0bd4b6c tests/e2e: fix e2e tests for ldap provider
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 10:10:11 +02:00
7c2decf5ec providers/ldap: squash migrations
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-14 09:22:25 +02:00
7f39399c32 providers/ldap: Added auto-generated uidNumber and guidNumber generated attributes for use with SSSD and similar software. (#1138)
* Added auto-generated uidNumber and guidNumber generated attributes for
use with SSSD and similar software.

The starting number for uid/gid can be configured iva environtment
variables and is by default 2000 which should work fine for most instances unless there are more than
999 local accounts on the server/computer.

The uidNumber is just the users Pk + the starting number.
The guidNumber is calculated by the last couple of bytes in the uuid of
the group + the starting number, this should have a low enough chance
for collisions that it's going to be fine for most use cases.

I have not added any interface stuff for configuring the environment variables as I couldn't really find my way around all the places I'd have to edit to add it and the default values should in my opinion be fine for 99% use cases.

* Add a 'fake' primary group for each user

* First attempt att adding config to interface

* Updated API to support new fields

* Refactor code, update documentation and remove obsolete comment

Simplify `GetRIDForGroup`, was a bit overcomplicated before.

Add an additional class/struct `LDAPGroup` which is the new argument
for `pi.GroupEntry` and util functions to create `LDAPGroup` from api.Group and api.User

Add proper support in the interface for changing gidNumber and uidNumber starting points

* make lint-fix for the migration files
2021-07-14 09:17:01 +02:00
7fd78a591d build(deps): bump boto3 from 1.17.110 to 1.17.111 (#1141) 2021-07-14 08:44:03 +02:00
bdb84b7a8f root: build bundled docs into helo dir to fix path issue with packaged static files
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 19:09:16 +02:00
84e9748340 policies/reputation: handle cache error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 18:47:32 +02:00
7dfc621ae4 LDAP Provider: TLS support (#1137) 2021-07-13 18:24:18 +02:00
cd0a6f2d7c website: upgrade to docusaurus 2beta3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:46:29 +02:00
b7835a751b website: migrate to react-before-after-slider-component
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:10:08 +02:00
fd197ceee7 website: fix broken links
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 12:02:14 +02:00
be5c8341d2 root: add bundled docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 11:06:51 +02:00
2036827f04 api: add sentry tunnel
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-13 10:58:14 +02:00
35665d248e build(deps): bump @typescript-eslint/eslint-plugin in /web (#1131)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.2 to 4.28.3.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.3/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 10:34:27 +02:00
bc30b41157 build(deps): bump @sentry/browser from 6.8.0 to 6.9.0 in /web (#1130)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.8.0 to 6.9.0.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/6.8.0...6.9.0)

---
updated-dependencies:
- dependency-name: "@sentry/browser"
  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-07-13 10:34:14 +02:00
2af7fab42c build(deps): bump @typescript-eslint/parser in /web (#1132) 2021-07-13 08:41:24 +02:00
4de205809b build(deps): bump @sentry/tracing from 6.8.0 to 6.9.0 in /web (#1133) 2021-07-13 08:41:14 +02:00
e8433472fd build(deps): bump boto3 from 1.17.109 to 1.17.110 (#1134) 2021-07-13 08:40:40 +02:00
3896299312 build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0 in /outpost (#1135) 2021-07-13 08:40:32 +02:00
5cfbb0993a Allow for Configurable Redis Port (#1124)
* root: make redis port configurable

* root: parse redis port from config as an integer

* code formatting

* lifecycle: truncate line under 100 chars

* lifecycle: incorrect indenting on newline
2021-07-12 11:01:41 +02:00
a62e3557ac build(deps): bump rollup from 2.52.8 to 2.53.1 in /web (#1125)
Bumps [rollup](https://github.com/rollup/rollup) from 2.52.8 to 2.53.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.52.8...v2.53.1)

---
updated-dependencies:
- dependency-name: rollup
  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-07-12 09:06:14 +02:00
626936636a build(deps): bump channels from 3.0.3 to 3.0.4 (#1126)
Bumps [channels](https://github.com/django/channels) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/django/channels/releases)
- [Changelog](https://github.com/django/channels/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/channels/compare/3.0.3...3.0.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 09:06:03 +02:00
85ec713213 build(deps): bump boto3 from 1.17.108 to 1.17.109 (#1127)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.108 to 1.17.109.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.108...1.17.109)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 09:05:54 +02:00
406bbdcfc9 root: fix missing go client in dockerfile
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-11 12:44:26 +02:00
02f87032cc Merge branch 'master' into inbuilt-proxy 2021-07-11 12:41:16 +02:00
b7a929d304 web/flows: update background for 2021.7
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 23:12:46 +02:00
3c0cc27ea1 events: fix error when slack notification request failed without a response
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:52:19 +02:00
ec254d5927 flows: allow variable substitution in flow titles
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:46:39 +02:00
92ba77e9e5 core: fix error when setting icon/background to url longer than 100 chars
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:31:32 +02:00
7ddb459030 web: fix error when showing error message of request
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-09 19:06:30 +02:00
076e89b600 build(deps): bump boto3 from 1.17.107 to 1.17.108 (#1122) 2021-07-09 10:05:20 +02:00
ba5fa2a04f build(deps): bump sentry-sdk from 1.2.0 to 1.3.0 (#1121) 2021-07-09 10:05:10 +02:00
90fe1c2ce8 providers/oauth2: allow blank redirect_uris to allow any redirect_uri
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-08 19:28:35 +02:00
85f88e785f build(deps): bump boto3 from 1.17.106 to 1.17.107 (#1120) 2021-07-08 09:50:29 +02:00
a7c4f81275 build(deps): bump rollup from 2.52.7 to 2.52.8 in /web (#1119) 2021-07-08 09:50:21 +02:00
396fbc4a76 build(deps): bump @types/grecaptcha from 3.0.2 to 3.0.3 in /web (#1114)
Bumps [@types/grecaptcha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/grecaptcha) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/grecaptcha)

---
updated-dependencies:
- dependency-name: "@types/grecaptcha"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:30:11 +02:00
2dcd0128aa build(deps): bump @types/chart.js from 2.9.33 to 2.9.34 in /web (#1115)
Bumps [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js) from 2.9.33 to 2.9.34.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

---
updated-dependencies:
- dependency-name: "@types/chart.js"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:29:57 +02:00
e5aa9e0774 build(deps): bump @types/codemirror from 5.60.1 to 5.60.2 in /web (#1116)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 5.60.1 to 5.60.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

---
updated-dependencies:
- dependency-name: "@types/codemirror"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:15:49 +02:00
53d78d561b build(deps): bump sentry-sdk from 1.1.0 to 1.2.0 (#1117)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/1.1.0...1.2.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  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-07-07 10:15:37 +02:00
93001d1329 build(deps): bump boto3 from 1.17.105 to 1.17.106 (#1118)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.105 to 1.17.106.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.105...1.17.106)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-07 10:15:26 +02:00
40428f5a82 providers/saml: fix parsing of POST bindings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 16:54:58 +02:00
007838fcf2 root: subclass SessionMiddleware to set Secure and SameSite flag depending on context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 14:48:36 +02:00
5e03b27348 website/docs: add note about logging out
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1113
2021-07-06 14:26:11 +02:00
7c51afa36c root: set samesite to None for SAML POST flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-06 12:39:51 +02:00
38fd5c5614 build(deps): bump @typescript-eslint/eslint-plugin in /web (#1112)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.28.1 to 4.28.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.28.2/packages/eslint-plugin)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-06 07:31:10 +00:00
7e3148fab5 build(deps): bump @typescript-eslint/parser in /web (#1111) 2021-07-06 08:58:10 +02:00
948db46406 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-07-05 19:11:26 +02:00
cccddd8c69 ci: re-finalize releases in sentry since sourcemaps are fixed now
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-05 18:30:11 +02:00
3dc9e247d5 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-07-02 16:23:30 +02:00
2a0bd50e23 outposts: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 17:08:12 +02:00
ff42663d3c root: more code merging
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 16:21:00 +02:00
ce49d7ea5b outposts: make managed outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-29 16:20:44 +02:00
8429dd19b2 Merge branch 'master' into inbuilt-proxy 2021-06-29 16:20:24 +02:00
1554dc9feb outposts: make outpost managed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-23 21:26:24 +02:00
1005f341e4 Merge branch 'master' into inbuilt-proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	internal/constants/constants.go
#	outpost/pkg/version.go
2021-06-23 20:41:06 +02:00
b98895ac2c root: add more common utils
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 17:29:01 +02:00
6dc38b0132 root: start deduplicating code
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:41:34 +02:00
e154e28611 root: fix build for main server
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:05:30 +02:00
690b7be1d8 root: initial merging of outpost and main project
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-06-16 12:02:02 +02:00
224 changed files with 12499 additions and 8124 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.6.4
current_version = 2021.7.3
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
@ -29,8 +29,6 @@ values =
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:outpost/pkg/version.go]
[bumpversion:file:web/src/constants.ts]
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]

View File

@ -3,3 +3,6 @@ static
htmlcov
*.env.yml
**/node_modules
dist/**
build/**
build_docs/**

View File

@ -9,7 +9,7 @@ updates:
assignees:
- BeryJu
- package-ecosystem: gomod
directory: "/outpost"
directory: "/"
schedule:
interval: daily
time: "04:00"
@ -48,11 +48,3 @@ updates:
open-pull-requests-limit: 10
assignees:
- BeryJu
- package-ecosystem: docker
directory: "/outpost"
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
assignees:
- BeryJu

19
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,19 @@
<!--
👋 Hello there! Welcome.
Please check the [Contributing guidelines](https://github.com/goauthentik/authentik/blob/master/CONTRIBUTING.md#how-can-i-contribute).
-->
# Details
* **Does this resolve an issue?**
Resolves #
## Changes
### New Features
* Adds feature which does x, y, and z.
### Breaking Changes
* Adds breaking change which causes \<issue\>.
## Additional
Any further notes or comments you want to make.

View File

@ -33,14 +33,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik:2021.6.4,
beryju/authentik:2021.7.3,
beryju/authentik:latest,
ghcr.io/goauthentik/server:2021.6.4,
ghcr.io/goauthentik/server:2021.7.3,
ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64
context: .
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.4', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
run: |
docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable
@ -75,14 +75,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-proxy:2021.6.4,
beryju/authentik-proxy:2021.7.3,
beryju/authentik-proxy:latest,
ghcr.io/goauthentik/proxy:2021.6.4,
ghcr.io/goauthentik/proxy:2021.7.3,
ghcr.io/goauthentik/proxy:latest
file: outpost/proxy.Dockerfile
file: proxy.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.4', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
run: |
docker pull beryju/authentik-proxy:latest
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
@ -117,14 +117,14 @@ jobs:
with:
push: ${{ github.event_name == 'release' }}
tags: |
beryju/authentik-ldap:2021.6.4,
beryju/authentik-ldap:2021.7.3,
beryju/authentik-ldap:latest,
ghcr.io/goauthentik/ldap:2021.6.4,
ghcr.io/goauthentik/ldap:2021.7.3,
ghcr.io/goauthentik/ldap:latest
file: outpost/ldap.Dockerfile
file: ldap.Dockerfile
platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable)
if: ${{ github.event_name == 'release' && !contains('2021.6.4', 'rc') }}
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
run: |
docker pull beryju/authentik-ldap:latest
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
@ -157,7 +157,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.2.0
uses: actions/setup-node@v2.3.0
with:
node-version: 12.x
- name: Build web api client and web ui
@ -176,7 +176,7 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
version: authentik@2021.6.4
version: authentik@2021.7.3
environment: beryjuorg-prod
sourcemaps: './web/dist'
finalize: false
url_prefix: '~/static/dist'

1
.gitignore vendored
View File

@ -200,3 +200,4 @@ media/
*mmdb
.idea/
/api/

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
hello@beryju.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

180
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,180 @@
# Contributing to authentik
:+1::tada: Thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to authentik and its components, which are hosted in the [goauthentik Organization](https://github.com/goauthentik) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table Of Contents
[Code of Conduct](#code-of-conduct)
[I don't want to read this whole thing, I just have a question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Atom and Packages](#atom-and-packages)
* [Atom Design Decisions](#design-decisions)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Your First Code Contribution](#your-first-code-contribution)
* [Pull Requests](#pull-requests)
[Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [JavaScript Styleguide](#javascript-styleguide)
* [CoffeeScript Styleguide](#coffeescript-styleguide)
* [Specs Styleguide](#specs-styleguide)
* [Documentation Styleguide](#documentation-styleguide)
[Additional Notes](#additional-notes)
* [Issue and Pull Request Labels](#issue-and-pull-request-labels)
## Code of Conduct
Basically, don't be a dickhead. This is an open-source non-profit project, that is made in the free time of Volunteers. If there's something you dislike or think can be done better, tell us! We'd love to hear any suggestions for improvement.
## I don't want to read this whole thing I just have a question!!!
Either [create a question on GitHub](https://github.com/goauthentik/authentik/issues/new?assignees=&labels=question&template=question.md&title=) or join [the Discord server](https://discord.gg/jg33eMhnj6)
## What should I know before I get started?
### The components
authentik consists of a few larger components:
- *authentik* the actual application server, is described below.
- *outpost-proxy* is a Go application based on a forked version of oauth2_proxy, which does identity-aware reverse proxying.
- *outpost-ldap* is a Go LDAP server that uses the *authentik* application server as its backend
- *web* is the web frontend, both for administrating and using authentik. It is written in TypeScript using lit-html and the PatternFly CSS Library.
- *website* is the Website/documentation, which uses docusaurus.
### authentik's structure
authentik is at it's very core a Django project. It consists of many individual django applications. These applications are intended to separate concerns, and they may share code between each other.
These are the current packages:
<a id="authentik-packages"/>
```
authentik
├── admin - Administrative tasks and APIs, no models (Version updates, Metrics, system tasks)
├── api - General API Configuration (Routes, Schema and general API utilities)
├── core - Core authentik functionality, central routes, core Models
├── crypto - Cryptography, currently used to generate and hold Certificates and Private Keys
├── events - Event Log, middleware and signals to generate signals
├── flows - Flows, the FlowPlanner and the FlowExecutor, used for all flows for authentication, authorization, etc
├── lib - Generic library of functions, few dependencies on other packages.
├── managed - Handle managed models and their state.
├── outposts - Configure and deploy outposts on kubernetes and docker.
├── policies - General PolicyEngine
│   ├── dummy - A Dummy policy used for testing
│   ├── event_matcher - Match events based on different criteria
│   ├── expiry - Check when a user's password was last set
│   ├── expression - Execute any arbitrary python code
│   ├── hibp - Check a password against HaveIBeenPwned
│   ├── password - Check a password against several rules
│   └── reputation - Check the user's/client's reputation
├── providers
│   ├── ldap - Provide LDAP access to authentik users/groups using an outpost
│   ├── oauth2 - OIDC-compliant OAuth2 provider
│   ├── proxy - Provides an identity-aware proxy using an outpost
│   └── saml - SAML2 Provider
├── recovery - Generate keys to use in case you lock yourself out
├── root - Root django application, contains global settings and routes
├── sources
│   ├── ldap - Sync LDAP users from OpenLDAP or Active Directory into authentik
│   ├── oauth - OAuth1 and OAuth2 Source
│   ├── plex - Plex source
│   └── saml - SAML2 Source
├── stages
│   ├── authenticator_duo - Configure a DUO authenticator
│   ├── authenticator_static - Configure TOTP backup keys
│   ├── authenticator_totp - Configure a TOTP authenticator
│   ├── authenticator_validate - Validate any authenticator
│   ├── authenticator_webauthn - Configure a WebAuthn authenticator
│   ├── captcha - Make the user pass a captcha
│   ├── consent - Let the user decide if they want to consent to an action
│   ├── deny - Static deny, can be used with policies
│   ├── dummy - Dummy stage to test
│   ├── email - Send the user an email and block execution until they click the link
│   ├── identification - Identify a user with any combination of fields
│   ├── invitation - Invitation system to limit flows to certain users
│   ├── password - Password authentication
│   ├── prompt - Arbitrary prompts
│   ├── user_delete - Delete the currently pending user
│   ├── user_login - Login the currently pending user
│   ├── user_logout - Logout the currently pending user
│   └── user_write - Write any currenetly pending data to the user.
└── tenants - Soft tennancy, configure defaults and branding per domain
```
This django project is running in gunicorn, which spawns multiple workers and threads. Gunicorn is run from a lightweight Go application which reverse-proxies it, handles static files and will eventually gain more functionality as more code is migrated to go.
There are also several background tasks which run in Celery, the root celery application is defined in `authentik.root.celery`.
## How Can I Contribute?
### Reporting Bugs
This section guides you through submitting a bug report for authentik. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Whenever authentik encounters an error, it will be logged as an Event with the type `system_exception`. This event type has a button to directly open a pre-filled GitHub issue form.
This form will have the full stack trace of the error that ocurred and shouldn't contain any sensitive data.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for authentik, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
When you are creating an enhancement suggestion, please fill in [the template](https://github.com/goauthentik/authentik/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=), including the steps that you imagine you would take if the feature you're requesting existed.
### Your First Code Contribution
#### Local development
authentik can be run locally, all though depending on which part you want to work on, different pre-requisites are required.
This is documented in the [developer docs](https://goauthentik.io/developer-docs/)
### Pull Requests
The process described here has several goals:
- Maintain authentik's quality
- Fix problems that are important to users
- Engage the community in working toward the best possible authentik
- Enable a sustainable system for authentik's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
1. Follow the [styleguides](#styleguides)
2. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing <details><summary>What if the status checks are failing?</summary>If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.</details>
3. Ensure your Code has tests. While it is not always possible to test every single case, the majority of the code should be tested.
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
## Styleguides
### Git Commit Messages
* Use the format of `<package>: <verb> <description>`
- See [here](#authentik-packages) for `package`
- Example: `providers/saml2: fix parsing of requests`
* Reference issues and pull requests liberally after the first line
### Python Styleguide
All Python code is linted with [black](https://black.readthedocs.io/en/stable/), [PyLint](https://www.pylint.org/) and [isort](https://pycqa.github.io/isort/).
authentik runs on Python 3.9 at the time of writing this.
* Use native type-annotations wherever possible.
* Add meaningful docstrings when possible.
* Ensure any database migrations work properly from the last stable version (this is checked via CI)
* If your code changes central functions, make sure nothing else is broken.
### Documentation Styleguide
* Use [MDX](https://mdxjs.com/) whenever appropriate.

View File

@ -10,8 +10,16 @@ RUN pip install pipenv && \
pipenv lock -r > requirements.txt && \
pipenv lock -r --dev-only > requirements-dev.txt
# Stage 2: Build web API
FROM openapitools/openapi-generator-cli as api-builder
# Stage 2: Build website
FROM node as website-builder
COPY ./website /static/
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-docs-only
# Stage 3: Build web API
FROM openapitools/openapi-generator-cli as web-api-builder
COPY ./schema.yml /local/schema.yml
@ -21,34 +29,52 @@ RUN docker-entrypoint.sh generate \
-o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
# Stage 3: Build webui
FROM node as npm-builder
# Stage 3: Generate API Client
FROM openapitools/openapi-generator-cli as go-api-builder
COPY ./schema.yml /local/schema.yml
RUN docker-entrypoint.sh generate \
--git-host goauthentik.io \
--git-repo-id outpost \
--git-user-id api \
-i /local/schema.yml \
-g go \
-o /local/api \
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true && \
rm -f /local/api/go.mod /local/api/go.sum
# Stage 4: Build webui
FROM node as web-builder
COPY ./web /static/
COPY --from=api-builder /local/web/api /static/api
COPY --from=web-api-builder /local/web/api /static/api
ENV NODE_ENV=production
RUN cd /static && npm i && npm run build
# Stage 4: Build go proxy
FROM golang:1.16.5 AS builder
# Stage 5: Build go proxy
FROM golang:1.16.6 AS builder
WORKDIR /work
COPY --from=npm-builder /static/robots.txt /work/web/robots.txt
COPY --from=npm-builder /static/security.txt /work/web/security.txt
COPY --from=npm-builder /static/dist/ /work/web/dist/
COPY --from=npm-builder /static/authentik/ /work/web/authentik/
COPY --from=web-builder /static/robots.txt /work/web/robots.txt
COPY --from=web-builder /static/security.txt /work/web/security.txt
COPY --from=web-builder /static/dist/ /work/web/dist/
COPY --from=web-builder /static/authentik/ /work/web/authentik/
COPY --from=website-builder /static/help/ /work/website/help/
COPY --from=go-api-builder /local/api api
COPY ./cmd /work/cmd
COPY ./web/static.go /work/web/static.go
COPY ./website/static.go /work/website/static.go
COPY ./internal /work/internal
COPY ./go.mod /work/go.mod
COPY ./go.sum /work/go.sum
RUN go build -o /work/authentik ./cmd/server/main.go
# Stage 5: Run
# Stage 6: Run
FROM python:3.9-slim-buster
WORKDIR /

View File

@ -32,7 +32,7 @@ gen-build:
gen-clean:
rm -rf web/api/src/
rm -rf outpost/api/
rm -rf api/
gen-web:
docker run \
@ -55,11 +55,14 @@ gen-outpost:
--git-user-id api \
-i /local/schema.yml \
-g go \
-o /local/outpost/api \
-o /local/api \
--additional-properties=packageName=api,enumClassPrefix=true,useOneOfDiscriminatorLookup=true
rm -f outpost/api/go.mod outpost/api/go.sum
rm -f api/go.mod api/go.sum
gen: gen-build gen-clean gen-web gen-outpost
migrate:
python -m lifecycle.migrate
run:
go run -v cmd/server/main.go

View File

@ -47,6 +47,7 @@ xmlsec = "*"
duo-client = "*"
ua-parser = "*"
deepmerge = "*"
colorama = "*"
[requires]
python_version = "3.9"

377
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f90d9fb4713eaf9c5ffe6a3858e64843670f79ab5007e7debf914c1f094c8d63"
"sha256": "e4f2e57bd5c709809515ab2b95eb3f5fa337d4a9334f4110a24bf28c3f9d5f8f"
},
"pipfile-spec": 6,
"requires": {
@ -122,19 +122,19 @@
},
"boto3": {
"hashes": [
"sha256:3b35689c215c982fe9f7ef78d748aa9b0cd15c3b2eb04f9b460aaa63fe2fbd03",
"sha256:b1cbeb92123799001b97f2ee1cdf470e21f1be08314ae28fc7ea357925186f1c"
"sha256:a012570d3535ec6c4db97e60ef51c2f39f38246429e1455cecc26c633ed81c10",
"sha256:c7f45b0417395d3020c98cdc10f942939883018210e29dbfe6fbfc0a74e503ec"
],
"index": "pypi",
"version": "==1.17.105"
"version": "==1.18.7"
},
"botocore": {
"hashes": [
"sha256:b0fda4edf8eb105453890700d49011ada576d0cc7326a0699dfabe9e872f552c",
"sha256:b5ba72d22212b0355f339c2a98b3296b3b2202a48e6a2b1366e866bc65a64b67"
"sha256:34c8b151a25616ed7791218f6d7780c3a97725fe3ceeaa28085b345a8513af6e",
"sha256:dcf399d21170bb899e00d2a693bddcc79e61471fbfead8500a65578700a3190a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.105"
"markers": "python_version >= '3.6'",
"version": "==1.21.7"
},
"cachetools": {
"hashes": [
@ -146,22 +146,22 @@
},
"cbor2": {
"hashes": [
"sha256:059363ae716c60f6ba29aa61b3d9c57896189c351c4119095f0542aec169e4dc",
"sha256:0b80a4a4fca830af3d3cf36b725c31f0a98106e9c2b02004ab73b0ec7f139446",
"sha256:0d22b47fb24b384200277fcfb0582c3a3551c413ad51f3bd3ee334caaf79a483",
"sha256:3c586a6e328ba5020802346f5e0304f81b982dcafeb51ee4109c9be9cccbc4a0",
"sha256:4dd142764607b1a8b5e3e3b474d2b84099e9cbb323596a15ee8db0d78901d95f",
"sha256:6f8a7911c2307ee8f8d4940bdcfb8bd21608f14203a83b651fcd7868bce377a5",
"sha256:7ecc4e9c548282a5d296d4535244efa69c7f67cda959f28e14929cf1d6af8a97",
"sha256:8bc9f5054650d05e6d3e90f6490dcd6ef6c01ad9c1568958a48dde2702824cb1",
"sha256:98410520482796a547af2d5ffe11a8a2dc3b9f2124834fa7c12db8264935ed61",
"sha256:a7926f7244b08c413f1a4fa71a81aa256771c75bdf1a4fd77308547a2d63dd48",
"sha256:ae31d3b5966807fdff6c9e6f894b0aa10474295d9ff8467a8b978a569c8fec47",
"sha256:ce6219986385778b1ab7f9b542f160bb4d3558f52975e914a27b774e47016fb7",
"sha256:d562b2773e14ee1d65ea5b85351a83a64d4f3fd011bc2b4c70a6e813e78203ce"
"sha256:0144ba1f44e4e36f7a8e8408eea72e1af6fc3ee42a704dacd4446307024e5231",
"sha256:15102b45dd8b1879b8743159af4538cbf4b3240fe3ebc4e747f6842cd7775888",
"sha256:40aa7c9dc9f69c38a2f9954e0adec266b04c55ed3188dc7a0213a92a2054220e",
"sha256:4b62aa7a95960d1c382e858c2c4cb24375cde3cae137d11875bb9a4667731011",
"sha256:6288b22cd3c0c842db2a4896473512fe83d24fa8ef4bc592d970635a2bb42e0e",
"sha256:81676dc7802029299dc168a1240cf1058c1fe5303fbc64598fe14bdb1f8bc076",
"sha256:8684c6ffbd35258cb9790ef2722559f585fb971288d6f55ee5efd9ba75dcc81b",
"sha256:8ce511337cbac10ccb97093649d6597aacb648ce3198e6afe8b4931fd1cabc61",
"sha256:986a8a9a4d3598008ece7241b746261118ef8d7c0efe7e6e9ce8b275f0421646",
"sha256:a8bf432f6cb595f50aeb8fed2a4aa3b3f7caa7f135fb57e4378eaa39242feac9",
"sha256:ba5e8065ca901ebec7ae390a183f3c13560454b6bd7dd81bf72c320e252b6461",
"sha256:d66350d1323460e1e9dcb2f9caa591b60833623f909173b840a0891a245cad83",
"sha256:e921d445575fbbe62ae68dc8ff3c6e05b341077fd24c6310c917b96fabe5e64a"
],
"markers": "python_version >= '3.6'",
"version": "==5.4.0"
"version": "==5.4.1"
},
"celery": {
"hashes": [
@ -180,65 +180,61 @@
},
"cffi": {
"hashes": [
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
"sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
"sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
"sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
"sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
"sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
"sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
"sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
"sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
"sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
"sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
"sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
"sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
"sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
"sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
"sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
"sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
"sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
"sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
"sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
"sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
"sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
"sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
"sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
"sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
"sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
"sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
"sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
"sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
"sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
"sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
"sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
"sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
"sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
"sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
"sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
"sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
"sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
"sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
"sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
"sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
"sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
"sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
"sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
"sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
"sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
],
"version": "==1.14.5"
"version": "==1.14.6"
},
"channels": {
"hashes": [
"sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f",
"sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"
"sha256:0ff0422b4224d10efac76e451575517f155fe7c97d369b5973b116f22eeaf86c",
"sha256:fdd9a94987a23d8d7ebd97498ed8b8cc83163f37e53fc6c85098aba7a3bb8b75"
],
"index": "pypi",
"version": "==3.0.3"
"version": "==3.0.4"
},
"channels-redis": {
"hashes": [
@ -256,6 +252,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"charset-normalizer": {
"hashes": [
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
],
"markers": "python_version >= '3'",
"version": "==2.0.3"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
@ -284,6 +288,14 @@
],
"version": "==0.2.0"
},
"colorama": {
"hashes": [
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
],
"index": "pypi",
"version": "==0.4.4"
},
"constantly": {
"hashes": [
"sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35",
@ -434,11 +446,11 @@
},
"drf-spectacular": {
"hashes": [
"sha256:6ffbfde7d96a4a2febd19182cc405217e1e86a50280fc739402291c93d1a32b7",
"sha256:77593024bb899f69227abedcf87def7851a11c9978f781aa4b385a10f67a38b7"
"sha256:f080128c42183fcaed6b9e8e5afcd2e5cd68426b1f80bfc85938f25e62db7fe5",
"sha256:fb19aa69fcfcd37b0c9dfb9989c0671e1bb47af332ca2171378c7f840263788c"
],
"index": "pypi",
"version": "==0.17.2"
"version": "==0.17.3"
},
"duo-client": {
"hashes": [
@ -473,11 +485,11 @@
},
"google-auth": {
"hashes": [
"sha256:9266252e11393943410354cf14a77bcca24dd2ccd9c4e1aef23034fe0fbae630",
"sha256:c7c215c74348ef24faef2f7b62f6d8e6b38824fe08b1e7b7b09a02d397eda7b3"
"sha256:036dd68c1e8baa422b6b61619b8e02793da2e20f55e69514612de6c080468755",
"sha256:7665c04f2df13cc938dc7d9066cddb1f8af62b038bc8b2306848c1b23121865f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.32.1"
"version": "==1.33.1"
},
"gunicorn": {
"hashes": [
@ -571,10 +583,10 @@
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"version": "==2.10"
"version": "==3.2"
},
"incremental": {
"hashes": [
@ -624,14 +636,14 @@
},
"ldap3": {
"hashes": [
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
"sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6",
"sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687",
"sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70",
"sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5",
"sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"
],
"index": "pypi",
"version": "==2.9"
"version": "==2.9.1"
},
"lxml": {
"hashes": [
@ -975,18 +987,18 @@
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
"version": "==2.8.2"
},
"python-dotenv": {
"hashes": [
"sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d",
"sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d"
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
],
"version": "==0.18.0"
"version": "==0.19.0"
},
"pytz": {
"hashes": [
@ -1040,11 +1052,11 @@
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"requests-oauthlib": {
"hashes": [
@ -1065,18 +1077,19 @@
},
"s3transfer": {
"hashes": [
"sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
"sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2"
"sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c",
"sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"
],
"version": "==0.4.2"
"markers": "python_version >= '3.6'",
"version": "==0.5.0"
},
"sentry-sdk": {
"hashes": [
"sha256:c1227d38dca315ba35182373f129c3e2722e8ed999e52584e6aca7d287870739",
"sha256:c7d380a21281e15be3d9f67a3c4fbb4f800c481d88ff8d8931f39486dd7b4ada"
"sha256:5210a712dd57d88d225c1fc3fe3a3626fee493637bcd54e204826cf04b8d769c",
"sha256:6864dcb6f7dec692635e5518c2a5c80010adf673c70340817f1a1b713d65bb41"
],
"index": "pypi",
"version": "==1.1.0"
"version": "==1.3.0"
},
"service-identity": {
"hashes": [
@ -1206,18 +1219,18 @@
},
"uvloop": {
"hashes": [
"sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc",
"sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69",
"sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01",
"sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d",
"sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760",
"sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c",
"sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47",
"sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c",
"sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c",
"sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"
"sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd",
"sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9",
"sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a",
"sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb",
"sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399",
"sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825",
"sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6",
"sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030",
"sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee",
"sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"
],
"version": "==0.15.2"
"version": "==0.15.3"
},
"vine": {
"hashes": [
@ -1423,11 +1436,11 @@
},
"astroid": {
"hashes": [
"sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892",
"sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9"
"sha256:7b963d1c590d490f60d2973e57437115978d3a2529843f160b5003b721e1e925",
"sha256:83e494b02d75d07d4e347b27c066fd791c0c74fc96c613d1ea3de0c82c48168f"
],
"markers": "python_version ~= '3.6'",
"version": "==2.6.2"
"version": "==2.6.5"
},
"attrs": {
"hashes": [
@ -1468,13 +1481,13 @@
],
"version": "==2021.5.30"
},
"chardet": {
"charset-normalizer": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
"sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1",
"sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
"markers": "python_version >= '3'",
"version": "==2.0.3"
},
"click": {
"hashes": [
@ -1568,10 +1581,10 @@
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"version": "==2.10"
"version": "==3.2"
},
"iniconfig": {
"hashes": [
@ -1582,11 +1595,11 @@
},
"isort": {
"hashes": [
"sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56",
"sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c"
"sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813",
"sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"
],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.9.1"
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
"version": "==5.9.2"
},
"lazy-object-proxy": {
"hashes": [
@ -1640,10 +1653,10 @@
},
"pathspec": {
"hashes": [
"sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
"sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.8.1"
"version": "==0.9.0"
},
"pbr": {
"hashes": [
@ -1671,11 +1684,11 @@
},
"pylint": {
"hashes": [
"sha256:23a1dc8b30459d78e9ff25942c61bb936108ccbe29dd9e71c01dc8274961709a",
"sha256:5d46330e6b8886c31b5e3aba5ff48c10f4aa5e76cbf9002c6544306221e63fbc"
"sha256:1f333dc72ef7f5ea166b3230936ebcfb1f3b722e76c980cb9fe6b9f95e8d3172",
"sha256:748f81e5776d6273a6619506e08f1b48ff9bcb8198366a56821cf11aac14fc87"
],
"index": "pypi",
"version": "==2.9.3"
"version": "==2.9.5"
},
"pylint-django": {
"hashes": [
@ -1753,53 +1766,57 @@
},
"regex": {
"hashes": [
"sha256:0e46c1191b2eb293a6912269ed08b4512e7e241bbf591f97e527492e04c77e93",
"sha256:18040755606b0c21281493ec309214bd61e41a170509e5014f41d6a5a586e161",
"sha256:1806370b2bef4d4193eebe8ee59a9fd7547836a34917b7badbe6561a8594d9cb",
"sha256:1ccbd41dbee3a31e18938096510b7d4ee53aa9fce2ee3dcc8ec82ae264f6acfd",
"sha256:1d386402ae7f3c9b107ae5863f7ecccb0167762c82a687ae6526b040feaa5ac6",
"sha256:210c359e6ee5b83f7d8c529ba3c75ba405481d50f35a420609b0db827e2e3bb5",
"sha256:268fe9dd1deb4a30c8593cabd63f7a241dfdc5bd9dd0233906c718db22cdd49a",
"sha256:361be4d311ac995a8c7ad577025a3ae3a538531b1f2cf32efd8b7e5d33a13e5a",
"sha256:3f7a92e60930f8fca2623d9e326c173b7cf2c8b7e4fdcf984b75a1d2fb08114d",
"sha256:444723ebaeb7fa8125f29c01a31101a3854ac3de293e317944022ae5effa53a4",
"sha256:494d0172774dc0beeea984b94c95389143db029575f7ca908edd74469321ea99",
"sha256:4b1999ef60c45357598935c12508abf56edbbb9c380df6f336de38a6c3a294ae",
"sha256:4fc86b729ab88fe8ac3ec92287df253c64aa71560d76da5acd8a2e245839c629",
"sha256:5049d00dbb78f9d166d1c704e93934d42cce0570842bb1a61695123d6b01de09",
"sha256:56bef6b414949e2c9acf96cb5d78de8b529c7b99752619494e78dc76f99fd005",
"sha256:59845101de68fd5d3a1145df9ea022e85ecd1b49300ea68307ad4302320f6f61",
"sha256:6b8b629f93246e507287ee07e26744beaffb4c56ed520576deac8b615bd76012",
"sha256:6c72ebb72e64e9bd195cb35a9b9bbfb955fd953b295255b8ae3e4ad4a146b615",
"sha256:7743798dfb573d006f1143d745bf17efad39775a5190b347da5d83079646be56",
"sha256:78a2a885345a2d60b5e68099e877757d5ed12e46ba1e87507175f14f80892af3",
"sha256:849802379a660206277675aa5a5c327f5c910c690649535863ddf329b0ba8c87",
"sha256:8cf6728f89b071bd3ab37cb8a0e306f4de897553a0ed07442015ee65fbf53d62",
"sha256:a1b6a3f600d6aff97e3f28c34192c9ed93fee293bd96ef327b64adb51a74b2f6",
"sha256:a548bb51c4476332ce4139df8e637386730f79a92652a907d12c696b6252b64d",
"sha256:a8a5826d8a1b64e2ff9af488cc179e1a4d0f144d11ce486a9f34ea38ccedf4ef",
"sha256:b024ee43ee6b310fad5acaee23e6485b21468718cb792a9d1693eecacc3f0b7e",
"sha256:b092754c06852e8a8b022004aff56c24b06310189186805800d09313c37ce1f8",
"sha256:b1dbeef938281f240347d50f28ae53c4b046a23389cd1fc4acec5ea0eae646a1",
"sha256:bf819c5b77ff44accc9a24e31f1f7ceaaf6c960816913ed3ef8443b9d20d81b6",
"sha256:c11f2fca544b5e30a0e813023196a63b1cb9869106ef9a26e9dae28bce3e4e26",
"sha256:ce269e903b00d1ab4746793e9c50a57eec5d5388681abef074d7b9a65748fca5",
"sha256:d0cf2651a8804f6325747c7e55e3be0f90ee2848e25d6b817aa2728d263f9abb",
"sha256:e07e92935040c67f49571779d115ecb3e727016d42fb36ee0d8757db4ca12ee0",
"sha256:e80d2851109e56420b71f9702ad1646e2f0364528adbf6af85527bc61e49f394",
"sha256:ed77b97896312bc2deafe137ca2626e8b63808f5bedb944f73665c68093688a7",
"sha256:f32f47fb22c988c0b35756024b61d156e5c4011cb8004aa53d93b03323c45657",
"sha256:fdad3122b69cdabdb3da4c2a4107875913ac78dab0117fc73f988ad589c66b66"
"sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f",
"sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad",
"sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a",
"sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf",
"sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59",
"sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d",
"sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895",
"sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4",
"sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3",
"sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222",
"sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0",
"sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c",
"sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417",
"sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d",
"sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d",
"sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761",
"sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0",
"sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026",
"sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854",
"sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb",
"sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d",
"sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068",
"sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde",
"sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d",
"sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec",
"sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa",
"sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd",
"sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b",
"sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26",
"sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2",
"sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f",
"sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694",
"sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0",
"sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407",
"sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874",
"sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035",
"sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d",
"sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c",
"sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5",
"sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985",
"sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"
],
"version": "==2021.7.1"
"version": "==2021.7.6"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"requests-mock": {
"hashes": [

View File

@ -2,10 +2,13 @@
## Supported Versions
(.x being the latest patch release for each version)
| Version | Supported |
| ---------- | ------------------ |
| 2021.4.x | :white_check_mark: |
| 2021.5.x | :white_check_mark: |
| 2021.6.x | :white_check_mark: |
| 2021.7.x | :white_check_mark: |
## Reporting a Vulnerability

View File

@ -1,3 +1,3 @@
"""authentik"""
__version__ = "2021.6.4"
__version__ = "2021.7.3"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -49,7 +49,7 @@ class ConfigView(APIView):
caps.append(Capabilities.CAN_GEO_IP)
if SERVICE_HOST_ENV_NAME in environ:
# Running in k8s, only s3 backup is supported
if CONFIG.y("postgresql.s3_backup"):
if CONFIG.y_bool("postgresql.s3_backup"):
caps.append(Capabilities.CAN_BACKUP)
else:
# Running in compose, backup is always supported

View File

@ -0,0 +1,38 @@
"""Sentry tunnel"""
from json import loads
from django.conf import settings
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.views.generic.base import View
from requests import post
from requests.exceptions import RequestException
from authentik.lib.config import CONFIG
class SentryTunnelView(View):
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
# Only allow usage of this endpoint when error reporting is enabled
if not CONFIG.y_bool("error_reporting.enabled", False):
return HttpResponse(status=400)
# Body is 2 json objects separated by \n
full_body = request.body
header = loads(full_body.splitlines()[0])
# Check that the DSN is what we expect
dsn = header.get("dsn", "")
if dsn != settings.SENTRY_DSN:
return HttpResponse(status=400)
response = post(
"https://sentry.beryju.org/api/8/envelope/",
data=full_body,
headers={"Content-Type": "application/octet-stream"},
)
try:
response.raise_for_status()
except RequestException:
return HttpResponse(status=500)
return HttpResponse(status=response.status_code)

View File

@ -1,5 +1,6 @@
"""api v2 urls"""
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from drf_spectacular.views import SpectacularAPIView
from rest_framework import routers
@ -10,6 +11,7 @@ from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionView
from authentik.admin.api.workers import WorkerView
from authentik.api.v2.config import ConfigView
from authentik.api.v2.sentry import SentryTunnelView
from authentik.api.views import APIBrowserView
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
@ -235,6 +237,7 @@ urlpatterns = (
FlowExecutorView.as_view(),
name="flow-executor",
),
path("sentry/", csrf_exempt(SentryTunnelView.as_view()), name="sentry"),
path("schema/", SpectacularAPIView.as_view(), name="schema"),
]
)

View File

@ -114,23 +114,23 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
},
)
@action(detail=True, methods=["GET"])
# pylint: disable=unused-argument
def check_access(self, request: Request, slug: str) -> Response:
"""Check access to a single application by slug"""
# Don't use self.get_object as that checks for view_application permission
# which the user might not have, even if they have access
application = get_object_or_404(Application, slug=slug)
# If the current user is superuser, they can set `for_user`
for_user = self.request.user
if self.request.user.is_superuser and "for_user" in request.data:
for_user = get_object_or_404(User, pk=request.data.get("for_user"))
engine = PolicyEngine(application, for_user, self.request)
for_user = request.user
if request.user.is_superuser and "for_user" in request.query_params:
for_user = get_object_or_404(User, pk=request.query_params.get("for_user"))
engine = PolicyEngine(application, for_user, request)
engine.use_cache = False
engine.build()
result = engine.result
response = PolicyTestResultSerializer(PolicyResult(False))
if result.passing:
response = PolicyTestResultSerializer(PolicyResult(True))
if self.request.user.is_superuser:
if request.user.is_superuser:
response = PolicyTestResultSerializer(result)
return Response(response.data)
@ -145,18 +145,20 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
)
def list(self, request: Request) -> Response:
"""Custom list method that checks Policy based access instead of guardian"""
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
should_cache = request.GET.get("search", "") == ""
superuser_full_list = (
str(request.GET.get("superuser_full_list", "false")).lower() == "true"
)
if superuser_full_list and request.user.is_superuser:
serializer = self.get_serializer(queryset, many=True)
return self.get_paginated_response(serializer.data)
return super().list(request)
# To prevent the user from having to double login when prompt is set to login
# and the user has just signed it. This session variable is set in the UserLoginStage
# and is (quite hackily) removed from the session in applications's API's List method
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)
allowed_applications = []
if not should_cache:

View File

@ -1,24 +1,81 @@
"""Groups API Viewset"""
from django.db.models.query import QuerySet
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer
from django_filters.filters import ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
from rest_framework.fields import BooleanField, CharField, JSONField
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict
from authentik.core.models import Group
from authentik.core.models import Group, User
class GroupMemberSerializer(ModelSerializer):
"""Stripped down user serializer to show relevant users for groups"""
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
attributes = JSONField(validators=[is_dict], required=False)
uid = CharField(read_only=True)
class Meta:
model = User
fields = [
"pk",
"username",
"name",
"is_active",
"last_login",
"is_superuser",
"email",
"avatar",
"attributes",
"uid",
]
class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False)
users_obj = ListSerializer(
child=GroupMemberSerializer(), read_only=True, source="users", required=False
)
class Meta:
model = Group
fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"]
fields = [
"pk",
"name",
"is_superuser",
"parent",
"users",
"attributes",
"users_obj",
]
class GroupFilter(FilterSet):
"""Filter for groups"""
members_by_username = ModelMultipleChoiceFilter(
field_name="users__username",
to_field_name="username",
queryset=User.objects.all(),
)
members_by_pk = ModelMultipleChoiceFilter(
field_name="users",
queryset=User.objects.all(),
)
class Meta:
model = Group
fields = ["name", "is_superuser", "members_by_pk", "members_by_username"]
class GroupViewSet(UsedByMixin, ModelViewSet):
@ -27,7 +84,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"]
filterset_class = GroupFilter
ordering = ["name"]
def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:

View File

@ -12,7 +12,7 @@ from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Token, TokenIntents
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents
from authentik.events.models import Event, EventAction
from authentik.managed.api import ManagedSerializer
@ -48,7 +48,7 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"""Token Viewset"""
lookup_field = "identifier"
queryset = Token.filter_not_expired()
queryset = Token.objects.all()
serializer_class = TokenSerializer
search_fields = [
"identifier",
@ -61,11 +61,19 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
"intent",
"user__username",
"description",
"expires",
"expiring",
]
ordering = ["expires"]
def perform_create(self, serializer: TokenSerializer):
serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
serializer.save(
user=self.request.user,
intent=TokenIntents.INTENT_API,
expiring=self.request.user.attributes.get(
USER_ATTRIBUTE_TOKEN_EXPIRING, True
),
)
@permission_required("authentik_core.view_token_key")
@extend_schema(

View File

@ -10,6 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.utils import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
@ -62,12 +63,40 @@ class UserSerializer(ModelSerializer):
]
class UserSelfSerializer(ModelSerializer):
"""User Serializer for information a user can retrieve about themselves and
update about themselves"""
is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True)
groups = ListSerializer(child=GroupSerializer(), read_only=True, source="ak_groups")
uid = CharField(read_only=True)
class Meta:
model = User
fields = [
"pk",
"username",
"name",
"is_active",
"is_superuser",
"groups",
"email",
"avatar",
"uid",
]
extra_kwargs = {
"is_active": {"read_only": True},
}
class SessionUserSerializer(PassiveSerializer):
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
and, if this user is being impersonated, the original user in the `original` property."""
user = UserSerializer()
original = UserSerializer(required=False)
user = UserSelfSerializer()
original = UserSelfSerializer(required=False)
class UserMetricsSerializer(PassiveSerializer):
@ -128,7 +157,14 @@ class UsersFilter(FilterSet):
class Meta:
model = User
fields = ["username", "name", "is_active", "is_superuser", "attributes"]
fields = [
"username",
"email",
"name",
"is_active",
"is_superuser",
"attributes",
]
class UserViewSet(UsedByMixin, ModelViewSet):
@ -136,7 +172,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
queryset = User.objects.none()
serializer_class = UserSerializer
search_fields = ["username", "name", "is_active"]
search_fields = ["username", "name", "is_active", "email"]
filterset_class = UsersFilter
def get_queryset(self): # pragma: no cover
@ -151,12 +187,36 @@ class UserViewSet(UsedByMixin, ModelViewSet):
data={"user": UserSerializer(request.user).data}
)
if SESSION_IMPERSONATE_USER in request._request.session:
serializer.initial_data["original"] = UserSerializer(
serializer.initial_data["original"] = UserSelfSerializer(
request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
).data
serializer.is_valid()
return Response(serializer.data)
@extend_schema(
request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)}
)
@action(
methods=["PUT"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[IsAuthenticated],
)
def update_self(self, request: Request) -> Response:
"""Allow users to change information on their own profile"""
data = UserSelfSerializer(
instance=User.objects.get(pk=request.user.pk), data=request.data
)
if not data.is_valid():
return Response(data.errors)
new_user = data.save()
# If we're impersonating, we need to update that user object
# since it caches the full object
if SESSION_IMPERSONATE_USER in request.session:
request.session[SESSION_IMPERSONATE_USER] = new_user
return self.me(request)
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[])
@ -206,6 +266,6 @@ class UserViewSet(UsedByMixin, ModelViewSet):
return queryset
def filter_queryset(self, queryset):
if self.request.user.has_perm("authentik_core.view_group"):
if self.request.user.has_perm("authentik_core.view_user"):
return self._filter_queryset_for_list(queryset)
return super().filter_queryset(queryset)

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.5 on 2021-07-09 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0025_alter_application_meta_icon"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_icon",
field=models.FileField(
default=None, max_length=500, null=True, upload_to="application-icons/"
),
),
]

View File

@ -37,6 +37,8 @@ LOGGER = get_logger()
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
GRAVATAR_URL = "https://secure.gravatar.com"
DEFAULT_AVATAR = static("dist/assets/images/user_default.png")
@ -228,7 +230,10 @@ class Application(PolicyBindingModel):
)
# For template applications, this can be set to /static/authentik/applications/*
meta_icon = models.FileField(
upload_to="application-icons/", default=None, null=True
upload_to="application-icons/",
default=None,
null=True,
max_length=500,
)
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)
@ -377,6 +382,13 @@ class ExpiringModel(models.Model):
expires = models.DateTimeField(default=default_token_duration)
expiring = models.BooleanField(default=True)
def expire_action(self, *args, **kwargs):
"""Handler which is called when this object is expired. By
default the object is deleted. This is less efficient compared
to bulk deleting objects, but classes like Token() need to change
values instead of being deleted."""
return self.delete(*args, **kwargs)
@classmethod
def filter_not_expired(cls, **kwargs) -> QuerySet:
"""Filer for tokens which are not expired yet or are not expiring,
@ -421,6 +433,19 @@ class Token(ManagedModel, ExpiringModel):
user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+")
description = models.TextField(default="", blank=True)
def expire_action(self, *args, **kwargs):
"""Handler which is called when this object is expired."""
from authentik.events.models import Event, EventAction
self.key = default_token_key()
self.expires = default_token_duration()
self.save(*args, **kwargs)
Event.new(
action=EventAction.SECRET_ROTATE,
token=self,
message=f"Token {self.identifier}'s secret was rotated.",
).save()
def __str__(self):
description = f"{self.identifier}"
if self.expiring:
@ -467,8 +492,8 @@ class PropertyMapping(SerializerModel, ManagedModel):
evaluator.set_context(user, request, self, **kwargs)
try:
return evaluator.evaluate(self.expression)
except (ValueError, SyntaxError) as exc:
raise PropertyMappingExpressionException from exc
except Exception as exc:
raise PropertyMappingExpressionException(str(exc)) from exc
def __str__(self):
return f"Property Mapping {self.name}"

View File

@ -141,11 +141,11 @@ class SourceFlowManager:
self._logger.info("denying source because user exists", user=user)
return Action.DENY, None
# Should never get here as default enroll case is returned above.
return Action.DENY, None
return Action.DENY, None # pragma: no cover
def update_connection(
self, connection: UserSourceConnection, **kwargs
) -> UserSourceConnection:
) -> UserSourceConnection: # pragma: no cover
"""Optionally make changes to the connection after it is looked up/created."""
return connection
@ -178,7 +178,7 @@ class SourceFlowManager:
% {"source": self.source.name}
),
)
return redirect("/")
return redirect(reverse("authentik_core:root-redirect"))
# pylint: disable=unused-argument
def get_stages_to_append(self, flow: Flow) -> list[Stage]:

View File

@ -26,14 +26,16 @@ def clean_expired_models(self: MonitoredTask):
messages = []
for cls in ExpiringModel.__subclasses__():
cls: ExpiringModel
amount, _ = (
objects = (
cls.objects.all()
.exclude(expiring=False)
.exclude(expiring=True, expires__gt=now())
.delete()
)
LOGGER.debug("Deleted expired models", model=cls, amount=amount)
messages.append(f"Deleted {amount} expired {cls._meta.verbose_name_plural}")
for obj in objects:
obj.expire_action()
amount = objects.count()
LOGGER.debug("Expired models", model=cls, amount=amount)
messages.append(f"Expired {amount} {cls._meta.verbose_name_plural}")
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))

View File

@ -31,7 +31,7 @@ class TestPropertyMappings(TestCase):
"""Test expression error"""
expr = "return aaa"
mapping = PropertyMapping.objects.create(name="test", expression=expr)
with self.assertRaises(NameError):
with self.assertRaises(PropertyMappingExpressionException):
mapping.evaluate(None, None)
events = Event.objects.filter(
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr
@ -44,7 +44,7 @@ class TestPropertyMappings(TestCase):
expr = "return aaa"
request = self.factory.get("/")
mapping = PropertyMapping.objects.create(name="test", expression=expr)
with self.assertRaises(NameError):
with self.assertRaises(PropertyMappingExpressionException):
mapping.evaluate(get_anonymous_user(), request)
events = Event.objects.filter(
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr

View File

@ -0,0 +1,160 @@
"""Test Source flow_manager"""
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest
from django.test import TestCase
from django.test.client import RequestFactory
from guardian.utils import get_anonymous_user
from authentik.core.models import SourceUserMatchingModes, User
from authentik.core.sources.flow_manager import Action
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import generate_client_id
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.views.callback import OAuthSourceFlowManager
class TestSourceFlowManager(TestCase):
"""Test Source flow_manager"""
def setUp(self) -> None:
super().setUp()
self.source = OAuthSource.objects.create(name="test")
self.factory = RequestFactory()
self.identifier = generate_client_id()
def get_request(self, user: User) -> HttpRequest:
"""Helper to create a get request with session and message middleware"""
request = self.factory.get("/")
request.user = user
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
middleware = MessageMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
return request
def test_unauthenticated_enroll(self):
"""Test un-authenticated user enrolling"""
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()
def test_unauthenticated_auth(self):
"""Test un-authenticated user authenticating"""
UserOAuthSourceConnection.objects.create(
user=get_anonymous_user(), source=self.source, identifier=self.identifier
)
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.AUTH)
flow_manager.get_flow()
def test_authenticated_link(self):
"""Test authenticated user linking"""
UserOAuthSourceConnection.objects.create(
user=get_anonymous_user(), source=self.source, identifier=self.identifier
)
user = User.objects.create(username="foo", email="foo@bar.baz")
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(user), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_email(self):
"""Test un-authenticated user enrolling (link on email)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.EMAIL_LINK
# Without email, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
# With email
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"email": "foo@bar.baz"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_username(self):
"""Test un-authenticated user enrolling (link on username)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_LINK
# Without username, deny
flow_manager = OAuthSourceFlowManager(
self.source, self.get_request(AnonymousUser()), self.identifier, {}
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.LINK)
flow_manager.get_flow()
def test_unauthenticated_enroll_username_deny(self):
"""Test un-authenticated user enrolling (deny on username)"""
User.objects.create(username="foo", email="foo@bar.baz")
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_DENY
# With non-existent username, enroll
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{
"username": "bar",
},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()
# With username
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.DENY)
flow_manager.get_flow()
def test_unauthenticated_enroll_link_non_existent(self):
"""Test un-authenticated user enrolling (link on username), username doesn't exist"""
self.source.user_matching_mode = SourceUserMatchingModes.USERNAME_LINK
flow_manager = OAuthSourceFlowManager(
self.source,
self.get_request(AnonymousUser()),
self.identifier,
{"username": "foo"},
)
action, _ = flow_manager.get_action()
self.assertEqual(action, Action.ENROLL)
flow_manager.get_flow()

View File

@ -1,18 +0,0 @@
"""authentik core task tests"""
from django.test import TestCase
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Token
from authentik.core.tasks import clean_expired_models
class TestTasks(TestCase):
"""Test Tasks"""
def test_token_cleanup(self):
"""Test Token cleanup task"""
Token.objects.create(expires=now(), user=get_anonymous_user())
self.assertEqual(Token.objects.all().count(), 1)
clean_expired_models.delay().get()
self.assertEqual(Token.objects.all().count(), 0)

View File

@ -1,8 +1,16 @@
"""Test token API"""
from django.urls.base import reverse
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from rest_framework.test import APITestCase
from authentik.core.models import Token, TokenIntents, User
from authentik.core.models import (
USER_ATTRIBUTE_TOKEN_EXPIRING,
Token,
TokenIntents,
User,
)
from authentik.core.tasks import clean_expired_models
class TestTokenAPI(APITestCase):
@ -22,3 +30,25 @@ class TestTokenAPI(APITestCase):
token = Token.objects.get(identifier="test-token")
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, True)
def test_token_create_non_expiring(self):
"""Test token creation endpoint"""
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = False
self.user.save()
response = self.client.post(
reverse("authentik_api:token-list"), {"identifier": "test-token"}
)
self.assertEqual(response.status_code, 201)
token = Token.objects.get(identifier="test-token")
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)
self.assertEqual(token.expiring, False)
def test_token_expire(self):
"""Test Token expire task"""
token: Token = Token.objects.create(expires=now(), user=get_anonymous_user())
key = token.key
clean_expired_models.delay().get()
token.refresh_from_db()
self.assertNotEqual(key, token.key)

View File

@ -0,0 +1,47 @@
# Generated by Django 3.2.5 on 2021-07-14 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0016_add_tenant"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@ -24,8 +24,10 @@ from authentik.events.geo import GEOIP_READER
from authentik.events.utils import cleanse_dict, get_user, model_to_dict, sanitize_dict
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage
from authentik.tenants.models import Tenant
from authentik.tenants.utils import DEFAULT_TENANT
LOGGER = get_logger("authentik.events")
@ -37,7 +39,8 @@ GAUGE_EVENTS = Gauge(
def default_event_duration():
"""Default duration an Event is saved"""
"""Default duration an Event is saved.
This is used as a fallback when no tenant is available"""
return now() + timedelta(days=365)
@ -62,6 +65,7 @@ class EventAction(models.TextChoices):
PASSWORD_SET = "password_set" # noqa # nosec
SECRET_VIEW = "secret_view" # noqa # nosec
SECRET_ROTATE = "secret_rotate" # noqa # nosec
INVITE_USED = "invitation_used"
@ -146,7 +150,12 @@ class Event(ExpiringModel):
"method": request.method,
}
if hasattr(request, "tenant"):
self.tenant = sanitize_dict(model_to_dict(request.tenant))
tenant: Tenant = request.tenant
# Because self.created only gets set on save, we can't use it's value here
# hence we set self.created to now and then use it
self.created = now()
self.expires = self.created + timedelta_from_string(tenant.event_retention)
self.tenant = sanitize_dict(model_to_dict(tenant))
if hasattr(request, "user"):
original_user = None
if hasattr(request, "session"):
@ -313,7 +322,8 @@ class NotificationTransport(models.Model):
response = post(self.webhook_url, json=body)
response.raise_for_status()
except RequestException as exc:
raise NotificationTransportError(exc.response.text) from exc
text = exc.response.text if exc.response else str(exc)
raise NotificationTransportError(text) from exc
return [
response.status_code,
response.text,

View File

@ -114,7 +114,7 @@ class MonitoredTask(Task):
# For tasks that should only be listed if they failed, set this to False
save_on_success: bool
_result: TaskResult
_result: Optional[TaskResult]
_uid: Optional[str]
@ -122,7 +122,7 @@ class MonitoredTask(Task):
super().__init__(*args, **kwargs)
self.save_on_success = True
self._uid = None
self._result = TaskResult(status=TaskResultStatus.ERROR, messages=[])
self._result = None
self.result_timeout_hours = 6
self.start = default_timer()
@ -138,25 +138,30 @@ class MonitoredTask(Task):
def after_return(
self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo
):
if not self._result.uid:
self._result.uid = self._uid
if self.save_on_success:
TaskInfo(
task_name=self.__name__,
task_description=self.__doc__,
start_timestamp=self.start,
finish_timestamp=default_timer(),
finish_time=datetime.now(),
result=self._result,
task_call_module=self.__module__,
task_call_func=self.__name__,
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
if self._result:
if not self._result.uid:
self._result.uid = self._uid
if self.save_on_success:
TaskInfo(
task_name=self.__name__,
task_description=self.__doc__,
start_timestamp=self.start,
finish_timestamp=default_timer(),
finish_time=datetime.now(),
result=self._result,
task_call_module=self.__module__,
task_call_func=self.__name__,
task_call_args=args,
task_call_kwargs=kwargs,
).save(self.result_timeout_hours)
return super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
# pylint: disable=too-many-arguments
def on_failure(self, exc, task_id, args, kwargs, einfo):
if not self._result:
self._result = TaskResult(
status=TaskResultStatus.ERROR, messages=[str(exc)]
)
if not self._result.uid:
self._result.uid = self._uid
TaskInfo(

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2021-07-09 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0022_alter_flowstagebinding_invalid_response_action"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="background",
field=models.FileField(
default=None,
help_text="Background shown during execution",
max_length=500,
null=True,
upload_to="flow-backgrounds/",
),
),
]

View File

@ -121,6 +121,7 @@ class Flow(SerializerModel, PolicyBindingModel):
default=None,
null=True,
help_text=_("Background shown during execution"),
max_length=500,
)
compatibility_mode = models.BooleanField(

View File

@ -17,7 +17,7 @@ from authentik.flows.challenge import (
WithUserInfoChallenge,
)
from authentik.flows.models import InvalidResponseAction
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
@ -102,12 +102,18 @@ class ChallengeStageView(StageView):
return self.challenge_invalid(challenge)
return self.challenge_valid(challenge)
def format_title(self) -> str:
"""Allow usage of placeholder in flow title."""
return self.executor.flow.title % {
"app": self.executor.plan.context.get(PLAN_CONTEXT_APPLICATION, "")
}
def _get_challenge(self, *args, **kwargs) -> Challenge:
challenge = self.get_challenge(*args, **kwargs)
if "flow_info" not in challenge.initial_data:
flow_info = ContextualFlowInfo(
data={
"title": self.executor.flow.title,
"title": self.format_title(),
"background": self.executor.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
}

View File

@ -26,7 +26,7 @@ from sentry_sdk import capture_exception
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict
from authentik.events.models import Event, EventAction, cleanse_dict
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
@ -52,6 +52,7 @@ from authentik.flows.planner import (
FlowPlanner,
)
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.tenants.models import Tenant
@ -203,6 +204,18 @@ class FlowExecutorView(APIView):
except InvalidStageError as exc:
return self.stage_invalid(str(exc))
def handle_exception(self, exc: Exception) -> HttpResponse:
"""Handle exception in stage execution"""
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
Event.new(
action=EventAction.SYSTEM_EXCEPTION,
message=exception_to_string(exc),
).from_http(self.request)
return to_stage_response(self.request, FlowErrorResponse(self.request, exc))
@extend_schema(
responses={
200: PolymorphicProxySerializer(
@ -237,11 +250,7 @@ class FlowExecutorView(APIView):
stage_response = self.current_stage_view.get(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
return self.handle_exception(exc)
@extend_schema(
responses={
@ -278,11 +287,7 @@ class FlowExecutorView(APIView):
stage_response = self.current_stage_view.post(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except
if settings.DEBUG or settings.TEST:
raise exc
capture_exception(exc)
self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
return self.handle_exception(exc)
def _initiate_plan(self) -> FlowPlan:
planner = FlowPlanner(self.flow)
@ -317,13 +322,15 @@ class FlowExecutorView(APIView):
"""User Successfully passed all stages"""
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
# extract the next param before cancel as that cleans it
next_param = None
if self.plan:
next_param = self.plan.context.get(PLAN_CONTEXT_REDIRECT)
if not next_param:
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:root-redirect"
)
if self.plan and PLAN_CONTEXT_REDIRECT in self.plan.context:
# The context `redirect` variable can only be set by
# an expression policy or authentik itself, so we don't
# check if its an absolute URL or a relative one
self.cancel()
return redirect(self.plan.context.get(PLAN_CONTEXT_REDIRECT))
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:root-redirect"
)
self.cancel()
return to_stage_response(self.request, redirect_with_qs(next_param))

View File

@ -13,7 +13,10 @@ web:
redis:
host: localhost
port: 6379
password: ''
tls: false
tls_reqs: "none"
cache_db: 0
message_queue_db: 1
ws_db: 2

View File

@ -0,0 +1,67 @@
"""Test HTTP Helpers"""
from django.test import RequestFactory, TestCase
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
Token,
TokenIntents,
User,
)
from authentik.lib.utils.http import (
OUTPOST_REMOTE_IP_HEADER,
OUTPOST_TOKEN_HEADER,
get_client_ip,
)
class TestHTTP(TestCase):
"""Test HTTP Helpers"""
def setUp(self) -> None:
self.user = User.objects.get(username="akadmin")
self.factory = RequestFactory()
def test_normal(self):
"""Test normal request"""
request = self.factory.get("/")
self.assertEqual(get_client_ip(request), "127.0.0.1")
def test_forward_for(self):
"""Test x-forwarded-for request"""
request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2")
self.assertEqual(get_client_ip(request), "127.0.0.2")
def test_fake_outpost(self):
"""Test faked IP which is overridden by an outpost"""
token = Token.objects.create(
identifier="test", user=self.user, intent=TokenIntents.INTENT_API
)
# Invalid, non-existant token
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: "abc",
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Invalid, user doesn't have permisions
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
},
)
self.assertEqual(get_client_ip(request), "127.0.0.1")
# Valid
self.user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
self.user.save()
request = self.factory.get(
"/",
**{
OUTPOST_REMOTE_IP_HEADER: "1.2.3.4",
OUTPOST_TOKEN_HEADER: token.key,
},
)
self.assertEqual(get_client_ip(request), "1.2.3.4")

View File

@ -2,10 +2,12 @@
from typing import Any, Optional
from django.http import HttpRequest
from structlog.stdlib import get_logger
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec
DEFAULT_IP = "255.255.255.255"
LOGGER = get_logger()
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
@ -27,23 +29,41 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
"""Get the actual remote IP when set by an outpost. Only
allowed when the request is authenticated, by a user with USER_ATTRIBUTE_CAN_OVERRIDE_IP set
to outpost"""
if not hasattr(request, "user"):
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
Token,
TokenIntents,
)
if (
OUTPOST_REMOTE_IP_HEADER not in request.META
or OUTPOST_TOKEN_HEADER not in request.META
):
return None
if not request.user.is_authenticated:
fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER]
tokens = Token.filter_not_expired(
key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API
)
if not tokens.exists():
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
return None
if OUTPOST_REMOTE_IP_HEADER not in request.META:
user = tokens.first().user
if not user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
LOGGER.warning(
"Remote-IP override: user doesn't have permission",
user=user,
fake_ip=fake_ip,
)
return None
if request.user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
return None
return request.META[OUTPOST_REMOTE_IP_HEADER]
return fake_ip
def get_client_ip(request: Optional[HttpRequest]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found"""
if request:
override = _get_outpost_override_ip(request)
if override:
return override
return _get_client_ip_from_meta(request.META)
return DEFAULT_IP
if not request:
return DEFAULT_IP
override = _get_outpost_override_ip(request)
if override:
return override
return _get_client_ip_from_meta(request.META)

View File

@ -5,12 +5,12 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
ALLOWED_KEYS = (
"days",
"seconds",
"microseconds",
"milliseconds",
"seconds",
"minutes",
"hours",
"days",
"weeks",
)

View File

@ -22,7 +22,7 @@ def redirect_with_qs(view: str, get_query_set=None, **kwargs) -> HttpResponse:
except NoReverseMatch:
if not is_url_absolute(view):
return redirect(view)
LOGGER.debug("redirect target is not a valid view", view=view)
LOGGER.warning("redirect target is not a valid view", view=view)
raise
else:
if get_query_set:

View File

@ -77,6 +77,7 @@ class OutpostSerializer(ModelSerializer):
"service_connection_obj",
"token_identifier",
"config",
"managed",
]
extra_kwargs = {"type": {"required": True}}

View File

@ -0,0 +1,18 @@
"""Outpost managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.outposts.models import Outpost, OutpostType
class OutpostManager(ObjectManager):
"""Outpost managed objects"""
def reconcile(self):
return [
EnsureExists(
Outpost,
"goauthentik.io/outposts/inbuilt",
name="authentik Bundeled Outpost",
object_field="name",
type=OutpostType.PROXY,
),
]

View File

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

View File

@ -28,12 +28,19 @@ from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.models import USER_ATTRIBUTE_SA, Provider, Token, TokenIntents, User
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
USER_ATTRIBUTE_SA,
Provider,
Token,
TokenIntents,
User,
)
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import USER_ATTRIBUTE_CAN_OVERRIDE_IP
from authentik.managed.models import ManagedModel
from authentik.outposts.controllers.k8s.utils import get_namespace
from authentik.outposts.docker_tls import DockerInlineTLS
@ -281,7 +288,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
verbose_name_plural = _("Kubernetes Service-Connections")
class Outpost(models.Model):
class Outpost(ManagedModel):
"""Outpost instance which manages a service user and token"""
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
@ -337,12 +344,13 @@ class Outpost(models.Model):
users = User.objects.filter(username=self.user_identifier)
if not users.exists():
user: User = User.objects.create(username=self.user_identifier)
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.set_unusable_password()
user.save()
else:
user = users.first()
user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.save()
# To ensure the user only has the correct permissions, we delete all of them and re-add
# the ones the user needs
with transaction.atomic():

View File

@ -28,6 +28,7 @@ from authentik.outposts.models import (
OutpostServiceConnection,
OutpostState,
OutpostType,
ServiceConnectionInvalid,
)
from authentik.providers.ldap.controllers.docker import LDAPDockerController
from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController
@ -114,7 +115,7 @@ def outpost_controller(
for log in logs:
LOGGER.debug(log)
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
except ControllerException as exc:
except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
else:
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))

View File

@ -0,0 +1,49 @@
# Generated by Django 3.2.5 on 2021-07-14 19:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0017_alter_eventmatcherpolicy_action"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="action",
field=models.TextField(
blank=True,
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("custom_", "Custom Prefix"),
],
help_text="Match created events with this action type. When left empty, all action types will be matched.",
),
),
]

View File

@ -21,12 +21,15 @@ def update_score(request: HttpRequest, username: str, amount: int):
"""Update score for IP and User"""
remote_ip = get_client_ip(request)
# We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
try:
# We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0, CACHE_TIMEOUT)
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
except ValueError as exc:
LOGGER.warning("failed to set reputation", exc=exc)
LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip)

View File

@ -17,6 +17,10 @@ class LDAPProviderSerializer(ProviderSerializer):
fields = ProviderSerializer.Meta.fields + [
"base_dn",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
"gid_start_number",
]
@ -44,6 +48,10 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"bind_flow_slug",
"application_slug",
"search_group",
"certificate",
"tls_server_name",
"uid_start_number",
"gid_start_number",
]

View File

@ -11,4 +11,5 @@ class LDAPDockerController(DockerController):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
]

View File

@ -11,4 +11,5 @@ class LDAPKubernetesController(KubernetesController):
super().__init__(outpost, connection)
self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 3.2.5 on 2021-07-13 11:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_crypto", "0002_create_self_signed_kp"),
("authentik_providers_ldap", "0002_ldapprovider_search_group"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="certificate",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_crypto.certificatekeypair",
),
),
migrations.AddField(
model_name="ldapprovider",
name="tls_server_name",
field=models.TextField(blank=True, default=""),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.5 on 2021-07-13 21:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0003_auto_20210713_1138"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="gid_start_number",
field=models.IntegerField(
default=4000,
help_text="The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
),
),
migrations.AddField(
model_name="ldapprovider",
name="uid_start_number",
field=models.IntegerField(
default=2000,
help_text="The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber",
),
),
]

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import Group, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.models import OutpostModel
@ -28,6 +29,36 @@ class LDAPProvider(OutpostModel, Provider):
),
)
tls_server_name = models.TextField(
default="",
blank=True,
)
certificate = models.ForeignKey(
CertificateKeyPair,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
uid_start_number = models.IntegerField(
default=2000,
help_text=_(
"The start for uidNumbers, this number is added to the user.Pk to make sure that the "
"numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't "
"collide with local users uidNumber"
),
)
gid_start_number = models.IntegerField(
default=4000,
help_text=_(
"The start for gidNumbers, this number is added to a number generated from the "
"group.Pk to make sure that the numbers aren't too low for POSIX groups. Default "
"is 4000 to ensure that we don't collide with local groups or users "
"primary groups gidNumber"
),
)
@property
def launch_url(self) -> Optional[str]:
"""LDAP never has a launch URL"""

View File

@ -109,6 +109,7 @@ class Migration(migrations.Migration):
"redirect_uris",
models.TextField(
default="",
blank=True,
help_text="Enter each URI on a new line.",
verbose_name="Redirect URIs",
),

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.5 on 2021-07-20 22:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0015_auto_20210703_1313"),
]
operations = [
migrations.AlterField(
model_name="authorizationcode",
name="nonce",
field=models.TextField(default=None, null=True, verbose_name="Nonce"),
),
]

View File

@ -158,6 +158,7 @@ class OAuth2Provider(Provider):
)
redirect_uris = models.TextField(
default="",
blank=True,
verbose_name=_("Redirect URIs"),
help_text=_("Enter each URI on a new line."),
)
@ -337,7 +338,7 @@ class AuthorizationCode(ExpiringModel, BaseGrantModel):
"""OAuth2 Authorization Code"""
code = models.CharField(max_length=255, unique=True, verbose_name=_("Code"))
nonce = models.TextField(blank=True, default="", verbose_name=_("Nonce"))
nonce = models.TextField(null=True, default=None, verbose_name=_("Nonce"))
is_open_id = models.BooleanField(
default=False, verbose_name=_("Is Authentication?")
)

View File

@ -67,7 +67,7 @@ class TestAuthorize(OAuthTestCase):
)
OAuthAuthorizationParams.from_request(request)
def test_redirect_uri(self):
def test_invalid_redirect_uri(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(
name="test",
@ -91,6 +91,28 @@ class TestAuthorize(OAuthTestCase):
)
OAuthAuthorizationParams.from_request(request)
def test_empty_redirect_uri(self):
"""test empty redirect URI (configure in provider)"""
OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
)
with self.assertRaises(RedirectUriError):
request = self.factory.get(
"/", data={"response_type": "code", "client_id": "test"}
)
OAuthAuthorizationParams.from_request(request)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://localhost",
},
)
OAuthAuthorizationParams.from_request(request)
def test_response_type(self):
"""test response_type"""
OAuth2Provider.objects.create(

View File

@ -0,0 +1,54 @@
"""JWKS tests"""
import json
from django.test import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.oauth2.models import OAuth2Provider
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestJWKS(OAuthTestCase):
"""Test JWKS view"""
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
def test_rs256(self):
"""Test JWKS request with RS256"""
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
rsa_key=CertificateKeyPair.objects.first(),
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse(
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
)
)
body = json.loads(force_str(response.content))
self.assertEqual(len(body["keys"]), 1)
def test_hs256(self):
"""Test JWKS request with HS256"""
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get(
reverse(
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
)
)
self.assertJSONEqual(force_str(response.content), {})

View File

@ -0,0 +1,111 @@
"""Test userinfo view"""
import json
from dataclasses import asdict
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import (
generate_client_id,
generate_client_secret,
)
from authentik.providers.oauth2.models import (
IDToken,
OAuth2Provider,
RefreshToken,
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestUserinfo(OAuthTestCase):
"""Test token view"""
def setUp(self) -> None:
super().setUp()
ObjectManager().run()
self.app = Application.objects.create(name="test", slug="test")
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="",
rsa_key=CertificateKeyPair.objects.first(),
)
self.provider.property_mappings.set(ScopeMapping.objects.all())
# Needs to be assigned to an application for iss to be set
self.app.provider = self.provider
self.app.save()
self.user = User.objects.get(username="akadmin")
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_client_id(),
refresh_token=generate_client_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
def test_userinfo_normal(self):
"""test user info with all normal scopes"""
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
def test_userinfo_invalid_scope(self):
"""test user info with a broken scope"""
scope = ScopeMapping.objects.create(
name="test", scope_name="openid", expression="q"
)
self.provider.property_mappings.add(scope)
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
)
self.assertTrue(events.exists())
self.assertEqual(
events.first().context["message"],
"Failed to evaluate property-mapping: name 'q' is not defined",
)

View File

@ -156,20 +156,23 @@ class OAuthAuthorizationParams:
def check_redirect_uri(self):
"""Redirect URI validation."""
allowed_redirect_urls = self.provider.redirect_uris.split()
if not self.redirect_uri:
LOGGER.warning("Missing redirect uri.")
raise RedirectUriError("", self.provider.redirect_uris.split())
if self.redirect_uri.lower() not in [
x.lower() for x in self.provider.redirect_uris.split()
]:
raise RedirectUriError("", allowed_redirect_urls)
if len(allowed_redirect_urls) < 1:
LOGGER.warning(
"Provider has no allowed redirect_uri set, allowing all.",
allow=self.redirect_uri.lower(),
)
return
if self.redirect_uri.lower() not in [x.lower() for x in allowed_redirect_urls]:
LOGGER.warning(
"Invalid redirect uri",
redirect_uri=self.redirect_uri,
excepted=self.provider.redirect_uris.split(),
)
raise RedirectUriError(
self.redirect_uri, self.provider.redirect_uris.split()
excepted=allowed_redirect_urls,
)
raise RedirectUriError(self.redirect_uri, allowed_redirect_urls)
if self.request:
raise AuthorizeError(
self.redirect_uri, "request_not_supported", self.grant_type, self.state
@ -189,6 +192,10 @@ class OAuthAuthorizationParams:
def check_nonce(self):
"""Nonce parameter validation."""
# https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
# Nonce is only required for Implicit flows
if self.grant_type != GrantTypes.IMPLICIT:
return
if not self.nonce:
self.nonce = self.state
LOGGER.warning("Using state as nonce for OpenID Request")

View File

@ -30,7 +30,7 @@ class JWKSView(View):
response_data = {}
if provider.jwt_alg == JWTAlgorithms.RS256:
if provider.jwt_alg == JWTAlgorithms.RS256 and provider.rsa_key:
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key()
public_numbers = public_key.public_numbers()
response_data["keys"] = [

View File

@ -126,7 +126,15 @@ class TokenParams:
LOGGER.warning("Missing authorization code")
raise TokenError("invalid_grant")
if self.redirect_uri not in self.provider.redirect_uris.split():
allowed_redirect_urls = self.provider.redirect_uris.split()
if len(allowed_redirect_urls) < 1:
LOGGER.warning(
"Provider has no allowed redirect_uri set, allowing all.",
allow=self.redirect_uri.lower(),
)
elif self.redirect_uri.lower() not in [
x.lower() for x in allowed_redirect_urls
]:
LOGGER.warning(
"Invalid redirect uri",
uri=self.redirect_uri,

View File

@ -7,6 +7,8 @@ from django.http.response import HttpResponseBadRequest
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.constants import (
SCOPE_GITHUB_ORG_READ,
SCOPE_GITHUB_USER,
@ -63,12 +65,20 @@ class UserInfoView(View):
for scope in ScopeMapping.objects.filter(
provider=token.provider, scope_name__in=scopes_from_client
).order_by("scope_name"):
value = scope.evaluate(
user=token.user,
request=self.request,
provider=token.provider,
token=token,
)
value = None
try:
value = scope.evaluate(
user=token.user,
request=self.request,
provider=token.provider,
token=token,
)
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=scope,
).from_http(self.request)
if value is None:
continue
if not isinstance(value, dict):

View File

@ -90,12 +90,6 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"""Proxy provider serializer for outposts"""
oidc_configuration = SerializerMethodField()
forward_auth_mode = SerializerMethodField()
def get_forward_auth_mode(self, instance: ProxyProvider) -> bool:
"""Legacy field for 2021.5 outposts"""
# TODO: remove in 2021.7
return instance.mode in [ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN]
class Meta:
@ -117,8 +111,6 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"basic_auth_user_attribute",
"mode",
"cookie_domain",
# Legacy field, remove in 2021.7
"forward_auth_mode",
]
@extend_schema_field(OpenIDConnectConfigurationSerializer)

View File

@ -60,12 +60,12 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
expected_hosts.sort()
expected_hosts_tls.sort()
have_hosts = [rule.host for rule in reference.spec.rules]
have_hosts = [rule.host for rule in current.spec.rules]
have_hosts.sort()
have_hosts_tls = []
for tls_config in reference.spec.tls:
if tls_config:
for tls_config in current.spec.tls:
if tls_config and tls_config.hosts:
have_hosts_tls += tls_config.hosts
have_hosts_tls.sort()

View File

@ -113,6 +113,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
authResponseHeaders=[
"Set-Cookie",
"X-Auth-Username",
"X-Auth-Groups",
"X-Forwarded-Email",
"X-Forwarded-Preferred-Username",
"X-Forwarded-User",

View File

@ -2,7 +2,7 @@
from xml.etree.ElementTree import ParseError # nosec
from defusedxml.ElementTree import fromstring
from django.http.response import HttpResponse
from django.http.response import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@ -116,7 +116,10 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
def metadata(self, request: Request, pk: int) -> Response:
"""Return metadata as XML string"""
# We don't use self.get_object() on purpose as this view is un-authenticated
provider = get_object_or_404(SAMLProvider, pk=pk)
try:
provider = get_object_or_404(SAMLProvider, pk=pk)
except ValueError:
raise Http404
try:
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
if "download" in request._request.GET:

View File

@ -9,6 +9,7 @@ from lxml.etree import Element, SubElement # nosec
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.request_parser import AuthNRequest
@ -99,7 +100,11 @@ class AssertionProcessor:
attribute_statement.append(attribute)
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=mapping,
).from_http(self.http_request)
continue
return attribute_statement
@ -158,10 +163,14 @@ class AssertionProcessor:
provider=self.provider,
)
if value is not None:
name_id.text = value
name_id.text = str(value)
return name_id
except PropertyMappingExpressionException as exc:
LOGGER.warning(str(exc))
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=self.provider.name_id_mapping,
).from_http(self.http_request)
return name_id
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL:
name_id.text = self.http_request.user.email

View File

@ -134,10 +134,18 @@ class ServiceProviderMetadataParser:
# For now we'll only look at the first descriptor.
# Even if multiple descriptors exist, we can only configure one
descriptor = sp_sso_descriptors[0]
auth_n_request_signed = (
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
)
assertion_signed = descriptor.attrib["WantAssertionsSigned"].lower() == "true"
auth_n_request_signed = False
if "AuthnRequestsSigned" in descriptor.attrib:
auth_n_request_signed = (
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
)
assertion_signed = False
if "WantAssertionsSigned" in descriptor.attrib:
assertion_signed = (
descriptor.attrib["WantAssertionsSigned"].lower() == "true"
)
acs_services = descriptor.findall(
f"{{{NS_SAML_METADATA}}}AssertionConsumerService"

View File

@ -1,7 +1,7 @@
"""SAML AuthNRequest Parser and dataclass"""
from base64 import b64decode
from dataclasses import dataclass
from typing import Optional
from typing import Optional, Union
from urllib.parse import quote_plus
import xmlsec
@ -54,7 +54,9 @@ class AuthNRequestParser:
def __init__(self, provider: SAMLProvider):
self.provider = provider
def _parse_xml(self, decoded_xml: str, relay_state: Optional[str]) -> AuthNRequest:
def _parse_xml(
self, decoded_xml: Union[str, bytes], relay_state: Optional[str]
) -> AuthNRequest:
root = ElementTree.fromstring(decoded_xml)
request_acs_url = root.attrib["AssertionConsumerServiceURL"]
@ -79,10 +81,12 @@ class AuthNRequestParser:
return auth_n_request
def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest:
def parse(
self, saml_request: str, relay_state: Optional[str] = None
) -> AuthNRequest:
"""Validate and parse raw request with enveloped signautre."""
try:
decoded_xml = b64decode(saml_request.encode()).decode()
decoded_xml = b64decode(saml_request.encode())
except UnicodeDecodeError:
raise CannotHandleAssertion(ERROR_CANNOT_DECODE_REQUEST)
@ -93,8 +97,9 @@ class AuthNRequestParser:
signature_nodes = root.xpath(
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
)
if len(signature_nodes) != 1:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
# No signatures, no verifier configured -> decode xml directly
if len(signature_nodes) < 1 and not verifier:
return self._parse_xml(decoded_xml, relay_state)
signature_node = signature_nodes[0]

View File

@ -20,6 +20,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata(self):
"""Test metadata export (normal)"""
self.client.logout()
provider = SAMLProvider.objects.create(
name="test",
authorization_flow=Flow.objects.get(
@ -34,6 +35,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata_download(self):
"""Test metadata export (download)"""
self.client.logout()
provider = SAMLProvider.objects.create(
name="test",
authorization_flow=Flow.objects.get(
@ -50,6 +52,7 @@ class TestSAMLProviderAPI(APITestCase):
def test_metadata_invalid(self):
"""Test metadata export (invalid)"""
self.client.logout()
# Provider without application
provider = SAMLProvider.objects.create(
name="test",
@ -61,6 +64,10 @@ class TestSAMLProviderAPI(APITestCase):
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
)
self.assertEqual(200, response.status_code)
response = self.client.get(
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}),
)
self.assertEqual(404, response.status_code)
def test_import_success(self):
"""Test metadata import (success case)"""

View File

@ -6,21 +6,40 @@ from django.http.request import QueryDict
from django.test import RequestFactory, TestCase
from guardian.utils import get_anonymous_user
from authentik.core.models import User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.flows.tests.test_planner import dummy_get_response
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser
from authentik.sources.saml.exceptions import MismatchedRequestID
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_UNSPECIFIED
from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
)
from authentik.sources.saml.processors.request import (
SESSION_REQUEST_ID,
RequestProcessor,
)
from authentik.sources.saml.processors.response import ResponseProcessor
POST_REQUEST = (
"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sMn"
"A9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSJo"
"dHRwczovL2V1LWNlbnRyYWwtMS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9hY3MvMmQ3MzdmOTYtNT"
"VmYi00MDM1LTk1M2UtNWUyNDEzNGViNzc4IiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZC5iZXJ5anUub3JnL2FwcGxpY2F0"
"aW9uL3NhbWwvYXdzLXNzby9zc28vYmluZGluZy9wb3N0LyIgSUQ9ImF3c19MRHhMR2V1YnBjNWx4MTJneENnUzZ1UGJpeD"
"F5ZDVyZSIgSXNzdWVJbnN0YW50PSIyMDIxLTA3LTA2VDE0OjIzOjA2LjM4OFoiIFByb3RvY29sQmluZGluZz0idXJuOm9h"
"c2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIH"
"htbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwczovL2V1LWNlbnRyYWwt"
"MS5zaWduaW4uYXdzLmFtYXpvbi5jb20vcGxhdGZvcm0vc2FtbC9kLTk5NjcyZjgyNzg8L3NhbWwyOklzc3Vlcj48c2FtbD"
"JwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWls"
"QWRkcmVzcyIvPjwvc2FtbDJwOkF1dGhuUmVxdWVzdD4="
)
REDIRECT_REQUEST = (
"fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu"
"EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H"
@ -63,6 +82,7 @@ class TestAuthNRequest(TestCase):
"""Test AuthN Request generator and parser"""
def setUp(self):
ObjectManager().run()
cert = CertificateKeyPair.objects.first()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=Flow.objects.get(
@ -208,3 +228,79 @@ class TestAuthNRequest(TestCase):
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED)
self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)
def test_signed_static(self):
"""Test post request with static request"""
provider = SAMLProvider(
name="aws",
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
acs_url=(
"https://eu-central-1.signin.aws.amazon.com/platform/"
"saml/acs/2d737f96-55fb-4035-953e-5e24134eb778"
),
audience="https://10.120.20.200/saml-sp/SAML2/POST",
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
signing_kp=CertificateKeyPair.objects.first(),
)
parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST)
self.assertEqual(parsed_request.id, "aws_LDxLGeubpc5lx12gxCgS6uPbix1yd5re")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
def test_request_attributes(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
self.assertIn("akadmin", response_proc.build_response())
def test_request_attributes_invalid(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = User.objects.get(username="akadmin")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# Create invalid PropertyMapping
scope = SAMLPropertyMapping.objects.create(
name="test", saml_name="test", expression="q"
)
self.provider.property_mappings.add(scope)
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
self.assertIn("akadmin", response_proc.build_response())
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
)
self.assertTrue(events.exists())
self.assertEqual(
events.first().context["message"],
"Failed to evaluate property-mapping: name 'q' is not defined",
)

View File

@ -27,6 +27,7 @@ class ChannelsStorage(BaseStorage):
uid,
{
"type": "event.update",
"message_type": "message",
"level": message.level_tag,
"tags": message.tags,
"message": message.message,

View File

@ -0,0 +1,94 @@
"""Dynamically set SameSite depending if the upstream connection is TLS or not"""
import time
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.contrib.sessions.exceptions import SessionInterrupted
from django.contrib.sessions.middleware import (
SessionMiddleware as UpstreamSessionMiddleware,
)
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.utils.cache import patch_vary_headers
from django.utils.http import http_date
class SessionMiddleware(UpstreamSessionMiddleware):
"""Dynamically set SameSite depending if the upstream connection is TLS or not"""
@staticmethod
def is_secure(request: HttpRequest) -> bool:
"""Check if request is TLS'd or localhost"""
if request.is_secure():
return True
host, _, _ = request.get_host().partition(":")
if host == "localhost" and settings.DEBUG:
# Since go does not consider localhost with http a secure origin
# we can't set the secure flag.
user_agent = request.META.get("HTTP_USER_AGENT", "")
if user_agent.startswith("authentik-outpost@"):
return False
return True
return False
def process_response(
self, request: HttpRequest, response: HttpResponse
) -> HttpResponse:
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
return response
# Set SameSite based on whether or not the request is secure
secure = SessionMiddleware.is_secure(request)
same_site = "None" if secure else "Lax"
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty.
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=same_site,
)
patch_vary_headers(response, ("Cookie",))
else:
if accessed:
patch_vary_headers(response, ("Cookie",))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SessionInterrupted(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=secure,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=same_site,
)
return response

View File

@ -188,12 +188,19 @@ REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
REDIS_PROTOCOL_PREFIX = "redis://"
REDIS_CELERY_TLS_REQUIREMENTS = ""
if CONFIG.y_bool("redis.tls", False):
REDIS_PROTOCOL_PREFIX = "rediss://"
REDIS_CELERY_TLS_REQUIREMENTS = f"?ssl_cert_reqs={CONFIG.y('redis.tls_reqs')}"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
f"/{CONFIG.y('redis.cache_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
f"{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:"
f"{int(CONFIG.y('redis.port'))}/{CONFIG.y('redis.cache_db')}"
),
"TIMEOUT": int(CONFIG.y("redis.cache_timeout", 300)),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
@ -203,14 +210,16 @@ DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_SAMESITE = "lax"
# Configured via custom SessionMiddleware
# SESSION_COOKIE_SAMESITE = "None"
# SESSION_COOKIE_SECURE = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
MIDDLEWARE = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"authentik.root.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"authentik.core.middleware.RequestIDMiddleware",
"authentik.tenants.middleware.TenantMiddleware",
@ -250,8 +259,9 @@ CHANNEL_LAYERS = {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
f"/{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')}"
],
},
},
@ -329,12 +339,16 @@ CELERY_BEAT_SCHEDULE = {
CELERY_TASK_CREATE_MISSING_QUEUES = True
CELERY_TASK_DEFAULT_QUEUE = "authentik"
CELERY_BROKER_URL = (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
f":6379/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
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 = (
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
f":6379/{CONFIG.y('redis.message_queue_db')}"
f"{REDIS_PROTOCOL_PREFIX}:"
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
@ -362,11 +376,12 @@ if CONFIG.y("postgresql.s3_backup"):
)
# Sentry integration
SENTRY_DSN = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
if _ERROR_REPORTING:
# pylint: disable=abstract-class-instantiated
sentry_init(
dsn="https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
dsn=SENTRY_DSN,
integrations=[
DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(),
@ -401,9 +416,8 @@ MEDIA_URL = "/media/"
TEST = False
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
LOG_LEVEL = CONFIG.y("log_level").upper() if not TEST else "DEBUG"
# 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"
structlog.configure_once(
processors=[

View File

@ -105,15 +105,17 @@ class LDAPPasswordChanger:
if len(user_attributes["sAMAccountName"]) >= 3:
if password.lower() in user_attributes["sAMAccountName"].lower():
return False
display_name_tokens = split(
RE_DISPLAYNAME_SEPARATORS, user_attributes["displayName"]
)
for token in display_name_tokens:
# Ignore tokens under 3 chars
if len(token) < 3:
continue
if token.lower() in password.lower():
return False
# No display name set, can't check any further
if len(user_attributes["displayName"]) < 1:
return True
for display_name in user_attributes["displayName"]:
display_name_tokens = split(RE_DISPLAYNAME_SEPARATORS, display_name)
for token in display_name_tokens:
# Ignore tokens under 3 chars
if len(token) < 3:
continue
if token.lower() in password.lower():
return False
return True
def ad_password_complexity(

View File

@ -5,6 +5,7 @@ from django.db.models.query import QuerySet
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -83,6 +84,11 @@ class BaseLDAPSynchronizer:
else:
properties[object_field] = self._flatten(value)
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=mapping,
).save()
self._logger.warning(
"Mapping failed to evaluate", exc=exc, mapping=mapping
)

View File

@ -5,6 +5,7 @@ from django.db.models import Q
from django.test import TestCase
from authentik.core.models import Group, User
from authentik.events.models import Event, EventAction
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -31,6 +32,33 @@ class LDAPSyncTests(TestCase):
additional_group_dn="ou=groups",
)
def test_sync_error(self):
"""Test user sync"""
self.source.property_mappings.set(
LDAPPropertyMapping.objects.filter(
Q(managed__startswith="goauthentik.io/sources/ldap/default")
| Q(managed__startswith="goauthentik.io/sources/ldap/ms")
)
)
mapping = LDAPPropertyMapping.objects.create(
name="name",
object_field="name",
expression="q",
)
self.source.property_mappings.set([mapping])
self.source.save()
connection = PropertyMock(return_value=mock_ad_connection(LDAP_PASSWORD))
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
user_sync = UserLDAPSynchronizer(self.source)
user_sync.sync()
self.assertFalse(User.objects.filter(username="user0_sn").exists())
self.assertFalse(User.objects.filter(username="user1_sn").exists())
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
context__message="Failed to evaluate property-mapping: name 'q' is not defined",
)
self.assertTrue(events.exists())
def test_sync_users_ad(self):
"""Test user sync"""
self.source.property_mappings.set(

View File

@ -0,0 +1,10 @@
"""Plex source settings"""
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"check_plex_token": {
"task": "authentik.sources.plex.tasks.check_plex_token_all",
"schedule": crontab(minute="31", hour="*/3"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -0,0 +1,43 @@
"""Plex tasks"""
from requests import RequestException
from authentik.events.models import Event, EventAction
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth
@CELERY_APP.task()
def check_plex_token_all():
"""Check plex token for all plex sources"""
for source in PlexSource.objects.all():
check_plex_token.delay(source.slug)
@CELERY_APP.task(bind=True, base=MonitoredTask)
def check_plex_token(self: MonitoredTask, source_slug: int):
"""Check the validity of a Plex source."""
sources = PlexSource.objects.filter(slug=source_slug)
if not sources.exists():
return
source: PlexSource = sources.first()
self.set_uid(source.slug)
auth = PlexAuth(source, source.plex_token)
try:
auth.get_user_info()
self.set_status(
TaskResult(TaskResultStatus.SUCCESSFUL, ["Plex token is valid."])
)
except RequestException as exc:
self.set_status(
TaskResult(
TaskResultStatus.ERROR,
["Plex token is invalid/an error occurred:", str(exc)],
)
)
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Plex token invalid, please re-authenticate source.\n{str(exc)}",
source=source,
).save()

View File

@ -1,10 +1,13 @@
"""plex Source tests"""
from django.test import TestCase
from requests.exceptions import RequestException
from requests_mock import Mocker
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.generators import generate_client_secret
from authentik.sources.plex.models import PlexSource
from authentik.sources.plex.plex import PlexAuth
from authentik.sources.plex.tasks import check_plex_token_all
USER_INFO_RESPONSE = {
"id": 1234123419,
@ -62,3 +65,18 @@ class TestPlexSource(TestCase):
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/resources", json=RESOURCES_RESPONSE)
self.assertTrue(api.check_server_overlap())
def test_check_task(self):
"""Test token check task"""
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/user", json=USER_INFO_RESPONSE)
check_plex_token_all()
self.assertFalse(
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
)
with Mocker() as mocker:
mocker.get("https://plex.tv/api/v2/user", exc=RequestException())
check_plex_token_all()
self.assertTrue(
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
)

View File

@ -67,10 +67,15 @@ class EmailStageView(ChallengeStageView):
"user": pending_user,
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
}
tokens = Token.filter_not_expired(**token_filters)
# Don't check for validity here, we only care if the token exists
tokens = Token.objects.filter(**token_filters)
if not tokens.exists():
return Token.objects.create(expires=now() + valid_delta, **token_filters)
return tokens.first()
token = tokens.first()
# Check if token is expired and rotate key if so
if token.is_expired:
token.expire_action()
return token
def send_email(self):
"""Helper function that sends the actual email. Implies that you've

View File

@ -91,7 +91,7 @@ def send_mail(
messages=["Successfully sent Mail."],
)
)
except (SMTPException, ConnectionError) as exc:
except (SMTPException, ConnectionError, OSError) as exc:
LOGGER.debug("Error sending email, retrying...", exc=exc)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
raise exc

View File

@ -1,18 +1,23 @@
"""invitation stage logic"""
from copy import deepcopy
from typing import Optional
from deepmerge import always_merger
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from structlog.stdlib import get_logger
from authentik.flows.models import in_memory_stage
from authentik.flows.stage import StageView
from authentik.flows.views import SESSION_KEY_GET
from authentik.stages.invitation.models import Invitation, InvitationStage
from authentik.stages.invitation.signals import invitation_used
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
INVITATION_TOKEN_KEY = "token" # nosec
INVITATION_IN_EFFECT = "invitation_in_effect"
INVITATION = "invitation"
class InvitationStageView(StageView):
@ -39,9 +44,37 @@ class InvitationStageView(StageView):
return self.executor.stage_invalid()
invite: Invitation = get_object_or_404(Invitation, pk=token)
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = deepcopy(invite.fixed_data)
self.executor.plan.context[INVITATION_IN_EFFECT] = True
self.executor.plan.context[INVITATION] = invite
context = {}
always_merger.merge(
context, self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {})
)
always_merger.merge(context, invite.fixed_data)
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = context
invitation_used.send(sender=self, request=request, invitation=invite)
if invite.single_use:
invite.delete()
self.executor.plan.append_stage(in_memory_stage(InvitationFinalStageView))
return self.executor.stage_ok()
class InvitationFinalStageView(StageView):
"""Final stage which is injected by invitation stage. Deletes
the used invitation."""
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Call get as this request may be called with post"""
return self.get(request, *args, **kwargs)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Delete invitation if single_use is active"""
invitation: Invitation = self.executor.plan.context.get(INVITATION, None)
if not invitation:
LOGGER.warning("InvitationFinalStageView stage called without invitation")
return HttpResponseBadRequest
if not invitation.single_use:
return self.executor.stage_ok()
invitation.delete()
return self.executor.stage_ok()

View File

@ -156,11 +156,13 @@ class TestUserLoginStage(TestCase):
base_url = reverse(
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
)
response = self.client.get(base_url)
response = self.client.get(base_url, follow=True)
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
self.assertEqual(
plan.context[PLAN_CONTEXT_PROMPT], data | plan.context[PLAN_CONTEXT_PROMPT]
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(

View File

@ -38,6 +38,7 @@ class TenantSerializer(ModelSerializer):
"flow_invalidation",
"flow_recovery",
"flow_unenrollment",
"event_retention",
]

View File

@ -3,6 +3,7 @@ from typing import Callable
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from sentry_sdk.api import set_tag
from authentik.tenants.utils import get_tenant_for_request
@ -19,4 +20,6 @@ class TenantMiddleware:
if not hasattr(request, "tenant"):
tenant = get_tenant_for_request(request)
setattr(request, "tenant", tenant)
set_tag("authentik.tenant_uuid", tenant.tenant_uuid.hex)
set_tag("authentik.tenant_domain", tenant.domain)
return self.get_response(request)

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2021-07-24 17:06
from django.db import migrations, models
import authentik.lib.utils.time
class Migration(migrations.Migration):
dependencies = [
("authentik_tenants", "0003_tenant_branding_favicon"),
]
operations = [
migrations.AddField(
model_name="tenant",
name="event_retention",
field=models.TextField(
default="days=365",
help_text="Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
]

View File

@ -5,6 +5,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from authentik.flows.models import Flow
from authentik.lib.utils.time import timedelta_string_validator
class Tenant(models.Model):
@ -22,6 +23,7 @@ class Tenant(models.Model):
)
branding_title = models.TextField(default="authentik")
branding_logo = models.TextField(
default="/static/dist/assets/icons/icon_left_brand.svg"
)
@ -40,6 +42,17 @@ class Tenant(models.Model):
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_unenrollment"
)
event_retention = models.TextField(
default="days=365",
validators=[timedelta_string_validator],
help_text=_(
(
"Events will be deleted after this duration."
"(Format: weeks=3;days=2;hours=3,seconds=2)."
)
),
)
def __str__(self) -> str:
if self.default:
return "Default tenant"

View File

@ -1,9 +1,12 @@
"""Test tenants"""
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.tenants.models import Tenant
@ -57,3 +60,28 @@ class TestTenants(TestCase):
"ui_footer_links": CONFIG.y("footer_links"),
},
)
def test_event_retention(self):
"""Test tenant's event retention"""
tenant = Tenant.objects.create(
domain="foo",
default=True,
branding_title="custom",
event_retention="weeks=3",
)
factory = RequestFactory()
request = factory.get("/")
request.tenant = tenant
event = Event.new(
action=EventAction.SYSTEM_EXCEPTION, message="test"
).from_http(request)
self.assertEqual(
event.expires.day, (event.created + timedelta_from_string("weeks=3")).day
)
self.assertEqual(
event.expires.month,
(event.created + timedelta_from_string("weeks=3")).month,
)
self.assertEqual(
event.expires.year, (event.created + timedelta_from_string("weeks=3")).year
)

View File

@ -6,12 +6,6 @@ trigger:
- next
- version-*
variables:
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
branchName: ${{ replace(variables['System.PullRequest.SourceBranch'], '/', '-') }}
${{ if startsWith(variables['Build.SourceBranch'], 'refs/heads/') }}:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
stages:
- stage: generate
jobs:
@ -27,7 +21,7 @@ stages:
script: make gen-outpost
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'outpost/api/'
targetPath: 'api/'
artifact: 'go_api_client'
publishLocation: 'pipeline'
- stage: lint
@ -43,17 +37,19 @@ stages:
inputs:
buildType: 'current'
artifactName: 'go_api_client'
path: "outpost/api/"
path: "api/"
- task: CmdLine@2
inputs:
script: |
mkdir -p web/dist
mkdir -p website/help
touch web/dist/test website/help/test
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci-lint run -v --timeout 200s
workingDirectory: 'outpost/'
- stage: build_docker
jobs:
- job: proxy_build_docker
@ -73,7 +69,7 @@ stages:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-proxy'
command: 'build'
Dockerfile: 'outpost/proxy.Dockerfile'
Dockerfile: 'proxy.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)
@ -106,7 +102,7 @@ stages:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/outpost-ldap'
command: 'build'
Dockerfile: 'outpost/ldap.Dockerfile'
Dockerfile: 'ldap.Dockerfile'
buildContext: '$(Build.SourcesDirectory)'
tags: |
gh-$(branchName)

View File

@ -14,13 +14,13 @@ resources:
- repo: self
variables:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
branchName: ${{ replace(variables['System.PullRequest.SourceBranch'], '/', '-') }}
${{ if startsWith(variables['Build.SourceBranch'], 'refs/heads/') }}:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
- name: POSTGRES_DB
value: authentik
- name: POSTGRES_USER
value: authentik
- name: POSTGRES_PASSWORD
value: "EK-5jnKfjrGRm<77"
- group: coverage
stages:
- stage: Lint_and_test
@ -397,6 +397,16 @@ stages:
- task: CmdLine@2
inputs:
script: bash <(curl -s https://codecov.io/bash)
- task: CmdLine@2
inputs:
script: |
npm install -g @zeus-ci/cli
npx zeus job update -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-cobertura+xml" coverage.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-e2e/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-integration/unittest.xml
npx zeus upload -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -t "application/x-junit+xml" coverage-unittest/unittest.xml
npx zeus job update --status=passed -b $BUILD_BUILDID -j $BUILD_BUILDNUMBER -r $BUILD_SOURCEVERSION
- stage: Build
jobs:
- job: build_server

View File

@ -2,16 +2,14 @@ package main
import (
"fmt"
"math/rand"
"net/url"
"os"
"os/signal"
"time"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/ldap"
"goauthentik.io/internal/common"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap"
)
const helpMessage = `authentik ldap
@ -23,32 +21,30 @@ Required environment variables:
func main() {
log.SetLevel(log.DebugLevel)
pbURL, found := os.LookupEnv("AUTHENTIK_HOST")
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found {
fmt.Println("env AUTHENTIK_HOST not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
akToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
if !found {
fmt.Println("env AUTHENTIK_TOKEN not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbURLActual, err := url.Parse(pbURL)
akURLActual, err := url.Parse(akURL)
if err != nil {
fmt.Println(err)
fmt.Println(helpMessage)
os.Exit(1)
}
rand.Seed(time.Now().UnixNano())
ex := common.Init()
defer common.Defer()
ac := ak.NewAPIController(*pbURLActual, pbToken)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
ac := ak.NewAPIController(*akURLActual, akToken)
ac.Server = ldap.NewServer(ac)
@ -58,7 +54,7 @@ func main() {
}
for {
<-interrupt
<-ex
ac.Shutdown()
os.Exit(0)
}

View File

@ -2,16 +2,14 @@ package main
import (
"fmt"
"math/rand"
"net/url"
"os"
"os/signal"
"time"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/proxy"
"goauthentik.io/internal/common"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxy"
)
const helpMessage = `authentik proxy
@ -23,32 +21,30 @@ Required environment variables:
func main() {
log.SetLevel(log.DebugLevel)
pbURL, found := os.LookupEnv("AUTHENTIK_HOST")
akURL, found := os.LookupEnv("AUTHENTIK_HOST")
if !found {
fmt.Println("env AUTHENTIK_HOST not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
akToken, found := os.LookupEnv("AUTHENTIK_TOKEN")
if !found {
fmt.Println("env AUTHENTIK_TOKEN not set!")
fmt.Println(helpMessage)
os.Exit(1)
}
pbURLActual, err := url.Parse(pbURL)
akURLActual, err := url.Parse(akURL)
if err != nil {
fmt.Println(err)
fmt.Println(helpMessage)
os.Exit(1)
}
rand.Seed(time.Now().UnixNano())
ex := common.Init()
defer common.Defer()
ac := ak.NewAPIController(*pbURLActual, pbToken)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
ac := ak.NewAPIController(*akURLActual, akToken)
ac.Server = proxy.NewServer(ac)
@ -58,7 +54,7 @@ func main() {
}
for {
<-interrupt
<-ex
ac.Shutdown()
os.Exit(0)
}

View File

@ -2,51 +2,106 @@ package main
import (
"fmt"
"sync"
"time"
"github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/common"
"goauthentik.io/internal/config"
"goauthentik.io/internal/constants"
"goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/web"
)
var running = true
func main() {
log.SetLevel(log.DebugLevel)
config.DefaultConfig()
config.LoadConfig("./authentik/lib/default.yml")
config.LoadConfig("./local.env.yml")
err := config.LoadConfig("./authentik/lib/default.yml")
if err != nil {
log.WithError(err).Warning("failed to load default config")
}
err = config.LoadConfig("./local.env.yml")
if err != nil {
log.WithError(err).Debug("failed to local config")
}
config.ConfigureLogger()
if config.G.ErrorReporting.Enabled {
sentry.Init(sentry.ClientOptions{
err := sentry.Init(sentry.ClientOptions{
Dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
AttachStacktrace: true,
TracesSampleRate: 0.6,
Release: fmt.Sprintf("authentik@%s", constants.VERSION),
Environment: config.G.ErrorReporting.Environment,
})
defer sentry.Flush(time.Second * 5)
defer sentry.Recover()
if err != nil {
log.WithError(err).Warning("failed to init sentry")
}
}
rl := log.WithField("logger", "authentik.g")
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
g := gounicorn.NewGoUnicorn()
for {
err := g.Start()
rl.WithError(err).Warning("gunicorn process died, restarting")
}
}()
go func() {
defer wg.Done()
ws := web.NewWebServer()
ws.Run()
}()
wg.Wait()
ex := common.Init()
defer common.Defer()
// u, _ := url.Parse("http://localhost:8000")
g := gounicorn.NewGoUnicorn()
ws := web.NewWebServer()
defer g.Kill()
defer ws.Shutdown()
for {
go attemptStartBackend(g)
ws.Start()
// go attemptProxyStart(u)
<-ex
running = false
log.WithField("logger", "authentik").Info("shutting down webserver")
go ws.Shutdown()
log.WithField("logger", "authentik").Info("killing gunicorn")
g.Kill()
}
}
func attemptStartBackend(g *gounicorn.GoUnicorn) {
for {
err := g.Start()
if !running {
return
}
log.WithField("logger", "authentik.g").WithError(err).Warning("gunicorn process died, restarting")
}
}
// func attemptProxyStart(u *url.URL) error {
// maxTries := 100
// attempt := 0
// for {
// log.WithField("logger", "authentik").Debug("attempting to init outpost")
// ac := ak.NewAPIController(*u, config.G.SecretKey)
// if ac == nil {
// attempt += 1
// time.Sleep(1 * time.Second)
// if attempt > maxTries {
// break
// }
// continue
// }
// ac.Server = proxy.NewServer(ac)
// err := ac.Start()
// log.WithField("logger", "authentik").Debug("attempting to start outpost")
// if err != nil {
// attempt += 1
// time.Sleep(5 * time.Second)
// if attempt > maxTries {
// break
// }
// continue
// }
// if !running {
// ac.Shutdown()
// return nil
// }
// }
// return nil
// }

View File

@ -21,7 +21,7 @@ services:
networks:
- internal
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.4}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
restart: unless-stopped
command: server
environment:
@ -44,7 +44,7 @@ services:
- "0.0.0.0:9000:9000"
- "0.0.0.0:9443:9443"
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.4}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
restart: unless-stopped
command: worker
networks:

50
go.mod
View File

@ -3,11 +3,49 @@ module goauthentik.io
go 1.16
require (
github.com/getsentry/sentry-go v0.10.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getsentry/sentry-go v0.11.0
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/runtime v0.19.29
github.com/go-openapi/strfmt v0.20.1
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-redis/redis/v7 v7.4.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/imdario/mergo v0.3.12
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/justinas/alice v1.2.0
github.com/kr/pretty v0.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/pires/go-proxyproto v0.6.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1
gopkg.in/yaml.v2 v2.3.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect
go.mongodb.org/mongo-driver v1.5.2 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0
)

859
go.sum

File diff suppressed because it is too large Load Diff

22
internal/common/global.go Normal file
View File

@ -0,0 +1,22 @@
package common
import (
"math/rand"
"os"
"os/signal"
"time"
"github.com/getsentry/sentry-go"
)
func Init() chan os.Signal {
rand.Seed(time.Now().UnixNano())
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
return interrupt
}
func Defer() {
defer sentry.Flush(time.Second * 5)
defer sentry.Recover()
}

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