Compare commits

..

160 Commits

Author SHA1 Message Date
e20bb7d636 release: 2021.4.3 2021-04-20 09:15:07 +02:00
e9abc25b92 website/docs: prepare changelog for 2021.4.3
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 21:08:10 +02:00
dc930c0cdf website/docs: manually set slug so release note URLs don't break
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 21:05:19 +02:00
464a1c0536 api: make 401 messages clearer
closes #755

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 20:46:57 +02:00
837d2f6fab outpost: use tools from docker (#758)
* outpost: replace golang.org/x/lint with golangci-lint

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

* outpost: use swagger generator from docker

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

* outpost: don't use tty for swagger gen

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

* outposts: revert docker-swagger gen

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 20:43:13 +02:00
8f00d73512 website: fix main site not rendering because <BrowserOnly>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 19:23:26 +02:00
b75feab709 outposts: don't run outpost_controller when no service connection is set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 19:23:26 +02:00
9c8433ec4d fix(docs/grafana): Fix a silly (#757) 2021-04-19 19:22:23 +02:00
ef080900a4 feat(docs/grafana): Add role mapping info (#756)
* feat(docs/grafana): Add role mapping info

* feat(docs/grafana): More info on role mappings
2021-04-19 19:07:09 +02:00
10b45a8dea api: fix 401 responses which should be 403s
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 19:03:00 +02:00
c43ac1f704 api: mount outposts under outposts/instances to match flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 18:51:12 +02:00
14d702450a core: add parameter to output property mapping test formatted
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 18:32:36 +02:00
0a1a2a035e web/admin: fix *Test Forms not having a default for codemirrors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 18:25:16 +02:00
ace777ebbe website: re-sort releases, add outposts to terminology
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 17:25:26 +02:00
8a6879afa5 core: add superuser_full_list to applications list, shows all applications when superuser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 16:07:30 +02:00
fdc7f14056 core: fix Tokens being created with incorrect intent by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 15:43:16 +02:00
8be80aaf9d api: fix CSRF error when using POST/PATCH/PUT in API Browser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 15:31:32 +02:00
e476f2dda2 website: bump deps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-19 09:28:48 +02:00
5d48cfab14 build(deps): bump @docusaurus/core in /website (#753) 2021-04-19 08:22:35 +02:00
1f22f0e7bb build(deps): bump chart.js from 3.1.0 to 3.1.1 in /web (#751) 2021-04-19 08:22:02 +02:00
ce082ead5e providers/oauth2: add unittests for authorize and token views
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 21:05:49 +02:00
dd2cd09637 web/admin: fix undefined being shown when viewing application
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 19:04:10 +02:00
828fe07fca website: dynamically load rapidoc to prevent react errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 17:58:36 +02:00
a074ea70e9 website/docs-dev: add initial translation docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 17:31:15 +02:00
84ce2c1df2 website: separate development docs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 17:25:58 +02:00
8628595590 website: add API Browser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 17:15:19 +02:00
7b8e5c4272 root: auto-migrate on startup, lock database using pg_advisory_lock
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 14:47:50 +02:00
caa5dc1d14 web/admin: improve default selection for property-mappings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 14:21:05 +02:00
f328b21e89 providers/oauth2: Set CORS Headers for token endpoint, check Origin header against redirect URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-18 14:20:50 +02:00
52abd959eb sources/oauth: save null instead of empty string for sources without configurable URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 21:15:06 +02:00
a0cd17a257 docs: add troubleshooting for permission issues
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 20:09:03 +02:00
32c5bf04b8 *: fix linting errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 20:08:49 +02:00
766c4873a0 web/admin: add ability to add users to a group whilst creating a group
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:56:49 +02:00
240136154b web/admin: fix default for codemirror
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:37:13 +02:00
78dd7b0341 web/admin: fix group member table order
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:36:55 +02:00
0021a93952 web/admin: fix non-matching provider type being selected when creating an OAuth Source
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:17:08 +02:00
67240fb9ad *: add model_name to TypeCreate API to pass to forms
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:12:00 +02:00
4add0bbe86 web/admin: fix provider type resetting when changing provider type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:06:56 +02:00
d2dd7d1366 sources/oauth: fix redirect loop for source with non-configurable URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 19:06:12 +02:00
476e57daa2 Merge branch 'version-2021.4'
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	docker-compose.yml
#	website/docs/installation/kubernetes.md
2021-04-17 16:01:35 +02:00
4eb8a0dcd1 docs: prepare changelog for 2021.4.2
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 15:29:55 +02:00
60615c9f3e release: 2021.4.2 2021-04-17 15:26:59 +02:00
b5b8573d87 core: fix propertymapping API returning invalid value for components
closes #746

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:05 +02:00
2e44c1cdfc sources/ldap: improve error handling during sync
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:05 +02:00
31909a4d78 outpost: fix outpost deps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:05 +02:00
4a444e667a root: base Websocket message storage on Base not fallback
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:05 +02:00
f67b57e369 flows: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
6be19962d2 outposts: bump go version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
262a9fa2a0 flows: annotate flows executor 404 error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
e8ba159756 root: fix setting of EMAIL_USE_TLS and EMAIL_USE_SSL
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
0b03d66a2f outposts: fix errors when creating multiple outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
7c858c9626 web/admin: fix errors in user profile when non-superuser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
71b6839d03 flows: include configure_flow in stages API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
ada49c077a web/admin: fix error when user doesn't have permissions to read source
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:04 +02:00
7880c7fb98 helm: make storage class, size and mode configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:03 +02:00
2b48ba4103 sources/oauth: fix resolution of sources' provider type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:03 +02:00
5e67f68f2b core: improve messaging when creating a recovery link for a user when no recovery flow exists
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:03 +02:00
1992b89154 sources/oauth: fix error when creating an oauth source which has fixed URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:01:03 +02:00
9ab2088ab7 helm: turn off monitoring by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:00:27 +02:00
a9d0d96418 root: add restart: unless-stopped to compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 12:00:27 +02:00
c476503594 web: fix background-color on router outlet on light mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 11:59:37 +02:00
de74f3ec1f core: fix propertymapping API returning invalid value for components
closes #746

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 11:50:28 +02:00
ce98255607 sources/ldap: improve error handling during sync
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 11:29:51 +02:00
53b9e5b93f outpost: fix outpost deps
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 11:16:40 +02:00
7aeb390eac docs: add note for minimum values.yaml file for k8s install
closes #745

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-17 11:06:21 +02:00
5df9ad63cf root: base Websocket message storage on Base not fallback
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 23:46:03 +02:00
e4400476a2 flows: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 23:15:06 +02:00
ef3c01ec34 outposts: bump go version
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 23:01:10 +02:00
b136d3bc69 flows: annotate flows executor 404 error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 22:56:44 +02:00
c34fcc73dc root: fix setting of EMAIL_USE_TLS and EMAIL_USE_SSL
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 22:44:42 +02:00
11b09c4ebd outposts: fix errors when creating multiple outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 22:43:35 +02:00
e32070ddeb web/admin: fix errors in user profile when non-superuser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 17:24:58 +02:00
33a8cea007 flows: include configure_flow in stages API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 17:19:48 +02:00
d01fd7cdb7 web/admin: fix error when user doesn't have permissions to read source
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 17:06:06 +02:00
1770e42cbf sources/oauth: add login with plex support
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 17:05:35 +02:00
2fed739be7 helm: make storage class, size and mode configurable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 12:31:55 +02:00
aa820b2b4d website: fix enrollment for keycloak
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 12:23:46 +02:00
582d2eb5eb sources/oauth: fix resolution of sources' provider type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 11:29:23 +02:00
c5e2635903 core: improve messaging when creating a recovery link for a user when no recovery flow exists
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 10:09:46 +02:00
cfe0a7a694 sources/oauth: fix error when creating an oauth source which has fixed URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 09:49:25 +02:00
c579540473 helm: turn off monitoring by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 09:20:17 +02:00
35f2b06611 build(deps): bump boto3 from 1.17.52 to 1.17.53 (#742)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.52 to 1.17.53.
- [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.52...1.17.53)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-16 09:07:17 +02:00
9c4f025d71 build(deps): bump @types/codemirror from 0.0.108 to 0.0.109 in /web (#743)
Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.108 to 0.0.109.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-16 09:07:06 +02:00
d8b8e8a5a3 root: add restart: unless-stopped to compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-16 09:03:58 +02:00
ec34c3eb75 website: fix azure ad application proxy on comparison
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 20:59:14 +02:00
0554c94c53 docs: add notes for openssl
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 17:33:25 +02:00
19a663a645 root: fix healthcheck part in docker-compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 17:28:38 +02:00
e72881b2a9 root: fix healthcheck part in docker-compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 17:20:14 +02:00
4452ff171e docs: add Explanation what containers do what
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 16:12:55 +02:00
39bdc3a9a9 website: fix enrollment for keycloack
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 13:12:50 +02:00
33bb6edf8c web: fix background-color on router outlet on light mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 11:31:06 +02:00
2eb18ff5e6 root: fix expired discord invite
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-15 10:54:43 +02:00
aeb1b5e8f2 build(deps): bump boto3 from 1.17.51 to 1.17.52 (#736) 2021-04-15 09:16:09 +02:00
bd8447d5a7 release: 2021.4.1 2021-04-14 09:46:16 +02:00
35fad191b8 Merge branch 'master' into version-2021.4 2021-04-14 09:27:11 +02:00
40a6f15cf1 build(deps): bump boto3 from 1.17.50 to 1.17.51 (#734) 2021-04-14 08:27:03 +02:00
420465981b build(deps): bump rollup from 2.45.1 to 2.45.2 in /web (#735) 2021-04-14 08:26:53 +02:00
4f9f936a7f Merge branch 'master' into version-2021.4 2021-04-13 23:16:35 +02:00
85c9fbe763 api: fix linting error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 21:49:47 +02:00
3d9874be69 api: fix error when authorization is empty
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 21:41:26 +02:00
9742d19729 Merge branch 'master' into version-2021.4 2021-04-13 21:07:20 +02:00
5a25e6d697 api: add legacy support for older outposts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 21:06:04 +02:00
7798a046db outpost: fix API calls being made with basic
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 20:50:45 +02:00
7a562fe8c0 Merge branch 'master' into version-2021.4 2021-04-13 20:02:25 +02:00
6821679fbc *: add support for bearer authentication on API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 20:01:30 +02:00
513d3c1c31 web: add support for PII for sentry, add user feedback dialog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 18:35:26 +02:00
30cb468ec5 website: fix search on docs site
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 18:05:19 +02:00
8b66fa55a6 web/elements: center header if no description is shown
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 17:46:03 +02:00
55bb9b6643 web/admin: show banner when backend and frontend versions mismatch
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-13 16:52:02 +02:00
1b79fad6cf build(deps): bump @typescript-eslint/eslint-plugin in /web (#732)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.21.0 to 4.22.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.22.0/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-13 10:28:22 +02:00
f9976492e7 build(deps): bump boto3 from 1.17.49 to 1.17.50 (#731)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.49 to 1.17.50.
- [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.49...1.17.50)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-13 09:42:16 +02:00
2fd0e46378 build(deps): bump @typescript-eslint/parser in /web (#733)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.21.0 to 4.22.0.
- [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.22.0/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-13 09:41:51 +02:00
fd0ad20031 release: 2021.4.1-rc2 2021-04-12 20:03:21 +02:00
13b75c15f0 outpost: download go-swagger from github (#730)
* outpost: download go-swagger from github

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

* outposts: use sudo to download swagger

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 20:01:12 +02:00
d329995740 docs: add algolia search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 18:25:52 +02:00
cd1b0c67ea web: fix text colour on initial load when not in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 12:25:44 +02:00
ab7941922f build(deps): bump @lingui/macro from 3.8.6 to 3.8.9 in /web (#722)
Bumps [@lingui/macro](https://github.com/lingui/js-lingui) from 3.8.6 to 3.8.9.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.8.6...v3.8.9)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-12 12:17:16 +02:00
e057d5fe0a root: fix lockfile
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 11:19:11 +02:00
3fb53e8311 build(deps-dev): bump pytest-django from 4.1.0 to 4.2.0 (#721)
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.1.0...v4.2.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens L <jens@beryju.org>
2021-04-12 10:57:46 +02:00
96b9d931f3 build(deps): bump @lingui/cli from 3.8.6 to 3.8.9 in /web (#725)
Bumps [@lingui/cli](https://github.com/lingui/js-lingui) from 3.8.6 to 3.8.9.
- [Release notes](https://github.com/lingui/js-lingui/releases)
- [Changelog](https://github.com/lingui/js-lingui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lingui/js-lingui/compare/v3.8.6...v3.8.9)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens L <jens@beryju.org>
2021-04-12 10:56:56 +02:00
a35f77c612 build(deps-dev): bump pylint-django from 2.4.2 to 2.4.3 (#729)
* build(deps-dev): bump pylint-django from 2.4.2 to 2.4.3

Bumps [pylint-django](https://github.com/PyCQA/pylint-django) from 2.4.2 to 2.4.3.
- [Release notes](https://github.com/PyCQA/pylint-django/releases)
- [Changelog](https://github.com/PyCQA/pylint-django/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/pylint-django/compare/v2.4.2...v2.4.3)

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

* root: fix pylint warning

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 09:33:56 +02:00
f287745c53 root: remove mapped port from server container
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-12 09:29:06 +02:00
65e09f92cd build(deps): bump boto3 from 1.17.48 to 1.17.49 (#720)
Bumps [boto3](https://github.com/boto/boto3) from 1.17.48 to 1.17.49.
- [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.48...1.17.49)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-12 09:28:23 +02:00
9b6446701e build(deps): bump chart.js from 3.0.2 to 3.1.0 in /web (#724)
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v3.0.2...v3.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-12 09:27:32 +02:00
71f7e23fe4 build(deps): bump eslint from 7.23.0 to 7.24.0 in /web (#723) 2021-04-12 08:31:28 +02:00
59eb89db6c build(deps): bump rollup from 2.44.0 to 2.45.1 in /web (#726) 2021-04-12 08:31:10 +02:00
939b55ce29 build(deps): bump @lingui/core from 3.8.6 to 3.8.9 in /web (#727) 2021-04-12 08:30:59 +02:00
7ba4e63c47 build(deps): bump postcss from 8.2.9 to 8.2.10 in /website (#728) 2021-04-12 08:30:49 +02:00
fae92f6bc8 *: fix JSONField overwriting required
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 23:20:45 +02:00
f9bf491240 stages/invitation: fix linting
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 23:09:09 +02:00
4f27a97e10 *: add validator to ensure JSON Fields only receive dicts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 23:05:19 +02:00
a0daaabfde web: replace full pf with components for loading animation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 22:02:48 +02:00
ea7ecb50c0 web: disable loading of roboto fonts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:52:01 +02:00
e7626d0716 Revert "release: 2021.4.1-rc1"
This reverts commit 2397cb162a.
2021-04-11 21:04:25 +02:00
e9d29b956d Merge branch 'master' into next 2021-04-11 21:02:59 +02:00
4a4ee98dec docs: fix typo in release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:49 +02:00
0d0baaa2f9 web/admin: fix missing css from ApplicationViewPage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:49 +02:00
1be1654bf2 web/elements: fix height when using PageHeader with Image
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:48 +02:00
ca51afb7df web: always set css variables
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:37 +02:00
11c8ae8f18 web/admin: remove sidebar box shadow
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:36 +02:00
858fcb8554 web/admin: classify no connected workers as error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:36 +02:00
571772854b web/admin: add tab to show events of a user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:35 +02:00
c91b40fc07 web/elements: use same icon for changelog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:35 +02:00
a736e708ae web/admin: use less generic slot names for ak-tabs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:24 +02:00
5c133a6c30 web/elements: make provider clickable for user's oauth codes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:23 +02:00
078dfb30f3 web/admin: make username in events log clickable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:22 +02:00
b526250515 web: fix header colour for notification drawer in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 21:02:21 +02:00
e52d397cb7 docs: fix typo in release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 20:40:53 +02:00
633029be3f web/admin: fix missing css from ApplicationViewPage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 19:44:29 +02:00
4147fbb839 web/elements: fix height when using PageHeader with Image
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 19:38:24 +02:00
430e3c576c web: always set css variables
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 19:00:29 +02:00
d6f60ad9ec web/admin: remove sidebar box shadow
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:56:34 +02:00
de6f663688 web/admin: classify no connected workers as error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:48:24 +02:00
fe17c3aa34 web/admin: add tab to show events of a user
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:46:26 +02:00
07b2525278 web/elements: use same icon for changelog
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:40:48 +02:00
9f758d19ba web/admin: use less generic slot names for ak-tabs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:40:01 +02:00
4216577565 web/elements: make provider clickable for user's oauth codes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 18:24:27 +02:00
f3396226e8 web/admin: make username in events log clickable
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 17:51:17 +02:00
ae7959ff51 web: fix header colour for notification drawer in dark mode
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 17:41:26 +02:00
b42b7be726 outpost: fix build dependencies
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-04-11 16:51:50 +02:00
169 changed files with 7456 additions and 6225 deletions

View File

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

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image - name: Building Docker Image
run: docker build run: docker build
--no-cache --no-cache
-t beryju/authentik:2021.4.1-rc1 -t beryju/authentik:2021.4.3
-t beryju/authentik:latest -t beryju/authentik:latest
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:2021.4.1-rc1 run: docker push beryju/authentik:2021.4.3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest run: docker push beryju/authentik:latest
build-proxy: build-proxy:
@ -48,11 +48,11 @@ jobs:
cd outpost/ cd outpost/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-proxy:2021.4.1-rc1 \ -t beryju/authentik-proxy:2021.4.3 \
-t beryju/authentik-proxy:latest \ -t beryju/authentik-proxy:latest \
-f proxy.Dockerfile . -f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:2021.4.1-rc1 run: docker push beryju/authentik-proxy:2021.4.3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest run: docker push beryju/authentik-proxy:latest
build-static: build-static:
@ -72,11 +72,11 @@ jobs:
cd web/ cd web/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-static:2021.4.1-rc1 \ -t beryju/authentik-static:2021.4.3 \
-t beryju/authentik-static:latest \ -t beryju/authentik-static:latest \
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:2021.4.1-rc1 run: docker push beryju/authentik-static:2021.4.3
- name: Push Docker Container to Registry (latest) - name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest run: docker push beryju/authentik-static:latest
test-release: test-release:
@ -110,5 +110,5 @@ jobs:
SENTRY_PROJECT: authentik SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
tagName: 2021.4.1-rc1 tagName: 2021.4.3
environment: beryjuorg-prod environment: beryjuorg-prod

207
Pipfile.lock generated
View File

@ -116,17 +116,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:e86c15049dc07cb67e8b466795f004f1f23c1acf078d47283cd5e4a692a5aa37" "sha256:1d26f6e7ae3c940cb07119077ac42485dcf99164350da0ab50d0f5ad345800cd",
"sha256:3bf3305571f3c8b738a53e9e7dcff59137dffe94670046c084a17f9fa4599ff3"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.48" "version": "==1.17.53"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:99c4f96bffae406f6dec1fa94e0886993e5bcd8657604d950abb43a49121fa7c", "sha256:d5e70d17b91c9b5867be7d6de0caa7dde9ed789bed62f03ea9b60718dc9350bf",
"sha256:af27a8133f5b8f6a55738ccd5efd35689f1822cfd447625a839ef7a991eab64f" "sha256:e303500c4e80f6a706602da53daa6f751cfa8f491665c99a24ee732ab6321573"
], ],
"version": "==1.20.48" "version": "==1.20.53"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -436,10 +437,10 @@
}, },
"google-auth": { "google-auth": {
"hashes": [ "hashes": [
"sha256:186fe2564634d67fbbb64f3daf8bc8c9cecbb2a7f535ed1a8a71795e50db8d87", "sha256:010f011c4e27d3d5eb01106fba6aac39d164842dfcd8709955c4638f5b11ccf8",
"sha256:70b39558712826e41f65e5f05a8d879361deaf84df8883e5dd0ec3d0da6ab66e" "sha256:f30a672a64d91cc2e3137765d088c5deec26416246f7a9e956eaf69a8d7ed49c"
], ],
"version": "==1.28.1" "version": "==1.29.0"
}, },
"gunicorn": { "gunicorn": {
"hashes": [ "hashes": [
@ -1105,10 +1106,10 @@
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:5d48b1fd2232141a9d5fb279709117aaba506cacea7f86f11bc392f06bfa8fc2", "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994",
"sha256:c5dadf598762899d8cfaecf68eba649cd25b0ce93b6c954b156aaa3eed160547" "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246"
], ],
"version": "==0.3.6" "version": "==0.3.7"
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
@ -1373,59 +1374,59 @@
}, },
"zope.interface": { "zope.interface": {
"hashes": [ "hashes": [
"sha256:02d3535aa18e34ce97c58d241120b7554f7d1cf4f8002fc9675cc7e7745d20e8", "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
"sha256:0378a42ec284b65706d9ef867600a4a31701a0d6773434e6537cfc744e3343f4", "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
"sha256:07d289358a8c565ea09e426590dd1179f93cf5ac3dd17d43fcc4fc63c1a9d275", "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
"sha256:0e6cdbdd94ae94d1433ab51f46a76df0f2cd041747c31baec1c1ffa4e76bd0c1", "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
"sha256:11354fb8b8bdc5cdd66358ed4f1f0ce739d78ff6d215d33b8f3ae282258c0f11", "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
"sha256:12588a46ae0a99f172c4524cbbc3bb870f32e0f8405e9fa11a5ef3fa3a808ad7", "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
"sha256:16caa44a06f6b0b2f7626ced4b193c1ae5d09c1b49c9b4962c93ae8aa2134f55", "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
"sha256:18c478b89b6505756f007dcf76a67224a23dcf0f365427742ed0c0473099caa4", "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
"sha256:221b41442cf4428fcda7fc958c9721c916709e2a3a9f584edd70f1493a09a762", "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
"sha256:26109c50ccbcc10f651f76277cfc05fba8418a907daccc300c9247f24b3158a2", "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
"sha256:28d8157f8c77662a1e0796a7d3cfa8910289131d4b4dd4e10b2686ab1309b67b", "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
"sha256:2c51689b7b40c7d9c7e8a678350e73dc647945a13b4e416e7a02bbf0c37bdb01", "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
"sha256:2ec58e1e1691dde4fbbd97f8610de0f8f1b1a38593653f7d3b8e931b9cd6d67f", "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
"sha256:416feb6500f7b6fc00d32271f6b8495e67188cb5eb51fc8e289b81fdf465a9cb", "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
"sha256:520352b18adea5478bbf387e9c77910a914985671fe36bc5ef19fdcb67a854bc", "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
"sha256:527415b5ca201b4add44026f70278fbc0b942cf0801a26ca5527cb0389b6151e", "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
"sha256:54243053316b5eec92affe43bbace7c8cd946bc0974a4aa39ff1371df0677b22", "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
"sha256:61b8454190b9cc87279232b6de28dee0bad040df879064bb2f0e505cda907918", "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
"sha256:672668729edcba0f2ee522ab177fcad91c81cfce991c24d8767765e2637d3515", "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
"sha256:67aa26097e194947d29f2b5a123830e03da1519bcce10cac034a51fcdb99c34f", "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
"sha256:6e7305e42b5f54e5ccf51820de46f0a7c951ba7cb9e3f519e908545b0f5628d0", "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
"sha256:7234ac6782ca43617de803735949f79b894f0c5d353fbc001d745503c69e6d1d", "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
"sha256:7426bea25bdf92f00fa52c7b30fcd2a2f71c21cf007178971b1f248b6c2d3145", "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
"sha256:74b331c5d5efdddf5bbd9e1f7d8cb91a0d6b9c4ba45ca3e9003047a84dca1a3b", "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
"sha256:79b6db1a18253db86e9bf1e99fa829d60fd3fc7ac04f4451c44e4bdcf6756a42", "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
"sha256:7d79cd354ae0a033ac7b86a2889c9e8bb0bb48243a6ed27fc5064ce49b842ada", "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
"sha256:823d1b4a6a028b8327e64865e2c81a8959ae9f4e7c9c8e0eec814f4f9b36b362", "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
"sha256:8715717a5861932b7fe7f3cbd498c82ff4132763e2fea182cc95e53850394ec1", "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
"sha256:89a6091f2d07936c8a96ce56f2000ecbef20fb420a94845e7d53913c558a6378", "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
"sha256:8af4b3116e4a37059bc8c7fe36d4a73d7c1d8802a1d8b6e549f1380d13a40160", "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
"sha256:8b4b0034e6c7f30133fa64a1cc276f8f1a155ef9529e7eb93a3c1728b40c0f5c", "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
"sha256:92195df3913c1de80062635bf64cd7bd0d0934a7fa1689b6d287d1cbbd16922c", "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
"sha256:96c2e68385f3848d58f19b2975a675532abdb65c8fa5f04d94b95b27b6b1ffa7", "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
"sha256:9c7044dbbf8c58420a9ef4ed6901f5a8b7698d90cd984d7f57a18c78474686f6", "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
"sha256:a1937efed7e3fe0ee74630e1960df887d8aa83c571e1cf4db9d15b9c181d457d", "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
"sha256:a38c10423a475a1658e2cb8f52cf84ec20a4c0adff724dd43a6b45183f499bc1", "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
"sha256:a413c424199bcbab71bf5fa7538246f27177fbd6dd74b2d9c5f34878658807f8", "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
"sha256:b18a855f8504743e0a2d8b75d008c7720d44e4c76687e13f959e35d9a13eb397", "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
"sha256:b4d59ab3608538e550a72cea13d3c209dd72b6e19e832688da7884081c01594e", "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
"sha256:b51d3f1cd87f488455f43046d72003689024b0fa9b2d53635db7523033b19996", "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
"sha256:c02105deda867d09cdd5088d08708f06d75759df6f83d8f7007b06f422908a30", "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
"sha256:c7b6032dc4490b0dcaf078f09f5b382dc35493cb7f473840368bf0de3196c2b6", "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
"sha256:c95b355dba2aaf5177dff943b25ded0529a7feb80021d5fdb114a99f0a1ef508", "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
"sha256:c980ae87863d76b1ea9a073d6d95554b4135032d34bc541be50c07d4a085821b", "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
"sha256:d12895cd083e35e9e032eb4b57645b91116f8979527381a8d864d1f6b8cb4a2e", "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
"sha256:d3cd9bad547a8e5fbe712a1dc1413aff1b917e8d39a2cd1389a6f933b7a21460", "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
"sha256:e8809b01f27f679e3023b9e2013051e0a3f17abff4228cb5197663afd8a0f2c7", "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
"sha256:f3c37b0dc1898e305aad4f7a1d75f6da83036588c28a9ce0afc681ff5245a601", "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
"sha256:f966765f54b536e791541458de84a737a6adba8467190f17a8fe7f85354ba908", "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
"sha256:fa939c2e2468142c9773443d4038e7c915b0cc1b670d3c9192bdc503f7ea73e9", "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
"sha256:fcc5c1f95102989d2e116ffc8467963554ce89f30a65a3ea86a4d06849c498d8" "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
], ],
"version": "==5.3.0" "version": "==5.4.0"
} }
}, },
"develop": { "develop": {
@ -1438,10 +1439,10 @@
}, },
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9", "sha256:ad63b8552c70939568966811a088ef0bc880f99a24a00834abd0e3681b514f91",
"sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df" "sha256:bea3f32799fbb8581f58431c12591bc20ce11cbc90ad82e2ea5717d94f2080d5"
], ],
"version": "==2.5.2" "version": "==2.5.3"
}, },
"attrs": { "attrs": {
"hashes": [ "hashes": [
@ -1661,11 +1662,11 @@
}, },
"pylint-django": { "pylint-django": {
"hashes": [ "hashes": [
"sha256:355dddb25ef07dbdb77a818b0860ada722aab654c24da34aab916ec26d6390ba", "sha256:a5a4515209a6237d1d390a4a307d53f53baaf4f058ecf4bb556c775d208f6b0d",
"sha256:f8d77f7da47a7019cda5cb669c214f03033208f9e945094661299d2637c0da06" "sha256:dc5ed27bb7662d73444ccd15a0b3964ed6ced6cc2712b85db616102062d2ec35"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.4.2" "version": "==2.4.3"
}, },
"pylint-plugin-utils": { "pylint-plugin-utils": {
"hashes": [ "hashes": [
@ -1691,11 +1692,11 @@
}, },
"pytest-django": { "pytest-django": {
"hashes": [ "hashes": [
"sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2", "sha256:80f8875226ec4dc0b205f0578072034563879d98d9b1bec143a80b9045716cb0",
"sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f" "sha256:a51150d8962200250e850c6adcab670779b9c2aa07271471059d1fb92a843fa9"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.0" "version": "==4.2.0"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
@ -1816,38 +1817,38 @@
}, },
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
"sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff",
"sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266",
"sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528",
"sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6",
"sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808",
"sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4",
"sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363",
"sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341",
"sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04",
"sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41",
"sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e",
"sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3",
"sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899",
"sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805",
"sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c",
"sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c",
"sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39",
"sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a",
"sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3",
"sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7",
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f",
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075",
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0",
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40",
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428",
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927",
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3",
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f",
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"
], ],
"version": "==1.4.2" "version": "==1.4.3"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [

View File

@ -4,7 +4,7 @@
--- ---
[![](https://img.shields.io/discord/809154715984199690?label=Discord&style=flat-square)](https://discord.gg/KPnmtNWy) [![](https://img.shields.io/discord/809154715984199690?label=Discord&style=flat-square)](https://discord.gg/jg33eMhnj6)
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/1?style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) [![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/1?style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
[![Tests](https://img.shields.io/azure-devops/tests/beryjuorg/authentik/1?compact_message&style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) [![Tests](https://img.shields.io/azure-devops/tests/beryjuorg/authentik/1?compact_message&style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
[![Code Coverage](https://img.shields.io/codecov/c/gh/beryju/authentik?style=flat-square)](https://codecov.io/gh/BeryJu/authentik) [![Code Coverage](https://img.shields.io/codecov/c/gh/beryju/authentik?style=flat-square)](https://codecov.io/gh/BeryJu/authentik)

View File

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

View File

@ -6,7 +6,7 @@ from drf_yasg.utils import swagger_auto_schema
from packaging.version import parse from packaging.version import parse
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
@ -50,7 +50,9 @@ class VersionSerializer(PassiveSerializer):
class VersionViewSet(ListModelMixin, GenericViewSet): class VersionViewSet(ListModelMixin, GenericViewSet):
"""Get running and latest version.""" """Get running and latest version."""
permission_classes = [IsAdminUser] permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
def get_queryset(self): # pragma: no cover def get_queryset(self): # pragma: no cover
return None return None

View File

@ -1,61 +1,65 @@
"""API Authentication""" """API Authentication"""
from base64 import b64decode from base64 import b64decode, b64encode
from binascii import Error from binascii import Error
from typing import Any, Optional, Union from typing import Any, Optional, Union
from rest_framework.authentication import BaseAuthentication, get_authorization_header from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.request import Request from rest_framework.request import Request
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import Token, TokenIntents, User from authentik.core.models import Token, TokenIntents, User
LOGGER = get_logger() LOGGER = get_logger()
X_AUTHENTIK_PREVENT_BASIC_HEADER = "HTTP_X_AUTHENTIK_PREVENT_BASIC"
# pylint: disable=too-many-return-statements
def token_from_header(raw_header: bytes) -> Optional[Token]: def token_from_header(raw_header: bytes) -> Optional[Token]:
"""raw_header in the Format of `Basic dGVzdDp0ZXN0`""" """raw_header in the Format of `Bearer dGVzdDp0ZXN0`"""
auth_credentials = raw_header.decode() auth_credentials = raw_header.decode()
# Accept headers with Type format and without if auth_credentials == "":
if " " in auth_credentials:
auth_type, auth_credentials = auth_credentials.split()
if auth_type.lower() != "basic":
LOGGER.debug(
"Unsupported authentication type, denying", type=auth_type.lower()
)
return None
try:
auth_credentials = b64decode(auth_credentials.encode()).decode()
except (UnicodeDecodeError, Error):
return None return None
# Accept credentials with username and without # Legacy, accept basic auth thats fully encoded (2021.3 outposts)
if ":" in auth_credentials: if " " not in auth_credentials:
_, password = auth_credentials.split(":") try:
else: plain = b64decode(auth_credentials.encode()).decode()
password = auth_credentials auth_type, body = plain.split()
auth_credentials = f"{auth_type} {b64encode(body.encode()).decode()}"
except (UnicodeDecodeError, Error):
raise AuthenticationFailed("Malformed header")
auth_type, auth_credentials = auth_credentials.split()
if auth_type.lower() not in ["basic", "bearer"]:
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
raise AuthenticationFailed("Unsupported authentication type")
password = auth_credentials
if auth_type.lower() == "basic":
try:
auth_credentials = b64decode(auth_credentials.encode()).decode()
except (UnicodeDecodeError, Error):
raise AuthenticationFailed("Malformed header")
# Accept credentials with username and without
if ":" in auth_credentials:
_, password = auth_credentials.split(":")
else:
password = auth_credentials
if password == "": # nosec if password == "": # nosec
return None raise AuthenticationFailed("Malformed header")
tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API) tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
if not tokens.exists(): if not tokens.exists():
LOGGER.debug("Token not found") raise AuthenticationFailed("Token invalid/expired")
return None
return tokens.first() return tokens.first()
class AuthentikTokenAuthentication(BaseAuthentication): class AuthentikTokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Basic authentication""" """Token-based authentication using HTTP Bearer authentication"""
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
"""Token-based authentication using HTTP Basic authentication""" """Token-based authentication using HTTP Bearer authentication"""
auth = get_authorization_header(request) auth = get_authorization_header(request)
token = token_from_header(auth) token = token_from_header(auth)
# None is only returned when the header isn't set.
if not token: if not token:
return None return None
return (token.user, None) return (token.user, None)
def authenticate_header(self, request: Request) -> str:
if X_AUTHENTIK_PREVENT_BASIC_HEADER in request._request.META:
return ""
return 'Basic realm="authentik"'

View File

@ -11,6 +11,29 @@ authentik API Browser
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<script>
function getCookie(name) {
let cookieValue = "";
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
window.addEventListener('DOMContentLoaded', (event) => {
const rapidocEl = document.querySelector('rapi-doc');
rapidocEl.addEventListener('before-try', (e) => {
e.detail.request.headers.append('X-CSRFToken', getCookie("authentik_csrf"));
});
});
</script>
<rapi-doc <rapi-doc
spec-url="{{ path }}" spec-url="{{ path }}"
heading-text="authentik" heading-text="authentik"

View File

@ -3,6 +3,7 @@ from base64 import b64encode
from django.test import TestCase from django.test import TestCase
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from rest_framework.exceptions import AuthenticationFailed
from authentik.api.auth import token_from_header from authentik.api.auth import token_from_header
from authentik.core.models import Token, TokenIntents from authentik.core.models import Token, TokenIntents
@ -11,7 +12,7 @@ from authentik.core.models import Token, TokenIntents
class TestAPIAuth(TestCase): class TestAPIAuth(TestCase):
"""Test API Authentication""" """Test API Authentication"""
def test_valid(self): def test_valid_basic(self):
"""Test valid token""" """Test valid token"""
token = Token.objects.create( token = Token.objects.create(
intent=TokenIntents.INTENT_API, user=get_anonymous_user() intent=TokenIntents.INTENT_API, user=get_anonymous_user()
@ -19,19 +20,30 @@ class TestAPIAuth(TestCase):
auth = b64encode(f":{token.key}".encode()).decode() auth = b64encode(f":{token.key}".encode()).decode()
self.assertEqual(token_from_header(f"Basic {auth}".encode()), token) self.assertEqual(token_from_header(f"Basic {auth}".encode()), token)
def test_valid_bearer(self):
"""Test valid token"""
token = Token.objects.create(
intent=TokenIntents.INTENT_API, user=get_anonymous_user()
)
self.assertEqual(token_from_header(f"Bearer {token.key}".encode()), token)
def test_invalid_type(self): def test_invalid_type(self):
"""Test invalid type""" """Test invalid type"""
self.assertIsNone(token_from_header("foo bar".encode())) with self.assertRaises(AuthenticationFailed):
token_from_header("foo bar".encode())
def test_invalid_decode(self): def test_invalid_decode(self):
"""Test invalid bas64""" """Test invalid bas64"""
self.assertIsNone(token_from_header("Basic bar".encode())) with self.assertRaises(AuthenticationFailed):
token_from_header("Basic bar".encode())
def test_invalid_empty_password(self): def test_invalid_empty_password(self):
"""Test invalid with empty password""" """Test invalid with empty password"""
self.assertIsNone(token_from_header("Basic :".encode())) with self.assertRaises(AuthenticationFailed):
token_from_header("Basic :".encode())
def test_invalid_no_token(self): def test_invalid_no_token(self):
"""Test invalid with no token""" """Test invalid with no token"""
auth = b64encode(":abc".encode()).decode() with self.assertRaises(AuthenticationFailed):
self.assertIsNone(token_from_header(f"Basic :{auth}".encode())) auth = b64encode(":abc".encode()).decode()
self.assertIsNone(token_from_header(f"Basic :{auth}".encode()))

View File

@ -113,6 +113,7 @@ router.register("core/user_consent", UserConsentViewSet)
router.register("core/tokens", TokenViewSet) router.register("core/tokens", TokenViewSet)
router.register("outposts/outposts", OutpostViewSet) router.register("outposts/outposts", OutpostViewSet)
router.register("outposts/instances", OutpostViewSet)
router.register("outposts/service_connections/all", ServiceConnectionViewSet) router.register("outposts/service_connections/all", ServiceConnectionViewSet)
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet) router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
router.register( router.register(

View File

@ -91,6 +91,15 @@ class ApplicationViewSet(ModelViewSet):
applications.append(application) applications.append(application)
return applications return applications
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
name="superuser_full_list",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
)
]
)
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""Custom list method that checks Policy based access instead of guardian""" """Custom list method that checks Policy based access instead of guardian"""
queryset = self._filter_queryset_for_list(self.get_queryset()) queryset = self._filter_queryset_for_list(self.get_queryset())
@ -98,6 +107,13 @@ class ApplicationViewSet(ModelViewSet):
should_cache = request.GET.get("search", "") == "" 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)
allowed_applications = [] allowed_applications = []
if not should_cache: if not should_cache:
allowed_applications = self._get_allowed_applications(queryset) allowed_applications = self._get_allowed_applications(queryset)

View File

@ -1,13 +1,17 @@
"""Groups API Viewset""" """Groups API Viewset"""
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import is_dict
from authentik.core.models import Group from authentik.core.models import Group
class GroupSerializer(ModelSerializer): class GroupSerializer(ModelSerializer):
"""Group Serializer""" """Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False)
class Meta: class Meta:
model = Group model = Group

View File

@ -1,6 +1,7 @@
"""PropertyMapping API Views""" """PropertyMapping API Views"""
from json import dumps from json import dumps
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins from rest_framework import mixins
@ -91,7 +92,9 @@ class PropertyMappingViewSet(
{ {
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass.component, # pyright: reportGeneralTypeIssues=false
"component": subclass().component,
"model_name": subclass._meta.model_name,
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@ -100,6 +103,13 @@ class PropertyMappingViewSet(
@swagger_auto_schema( @swagger_auto_schema(
request_body=PolicyTestSerializer(), request_body=PolicyTestSerializer(),
responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"}, responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"},
manual_parameters=[
openapi.Parameter(
name="format_result",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
)
],
) )
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
# pylint: disable=unused-argument, invalid-name # pylint: disable=unused-argument, invalid-name
@ -110,6 +120,8 @@ class PropertyMappingViewSet(
if not test_params.is_valid(): if not test_params.is_valid():
return Response(test_params.errors, status=400) return Response(test_params.errors, status=400)
format_result = str(request.GET.get("format_result", "false")).lower() == "true"
# User permission check, only allow mapping testing for users that are readable # User permission check, only allow mapping testing for users that are readable
users = get_objects_for_user(request.user, "authentik_core.view_user").filter( users = get_objects_for_user(request.user, "authentik_core.view_user").filter(
pk=test_params.validated_data["user"].pk pk=test_params.validated_data["user"].pk
@ -124,7 +136,9 @@ class PropertyMappingViewSet(
self.request, self.request,
**test_params.validated_data.get("context", {}), **test_params.validated_data.get("context", {}),
) )
response_data["result"] = dumps(result) response_data["result"] = dumps(
result, indent=(4 if format_result else None)
)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
response_data["result"] = str(exc) response_data["result"] = str(exc)
response_data["successful"] = False response_data["successful"] = False

View File

@ -78,6 +78,7 @@ class ProviderViewSet(
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name,
} }
) )
data.append( data.append(
@ -85,6 +86,7 @@ class ProviderViewSet(
"name": _("SAML Provider from Metadata"), "name": _("SAML Provider from Metadata"),
"description": _("Create a SAML Provider by importing its Metadata."), "description": _("Create a SAML Provider by importing its Metadata."),
"component": "ak-provider-saml-import-form", "component": "ak-provider-saml-import-form",
"model_name": "",
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -81,6 +81,7 @@ class SourceViewSet(
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": component, "component": component,
"model_name": subclass._meta.model_name,
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -11,7 +11,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.users import UserSerializer from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Token from authentik.core.models import Token, TokenIntents
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.managed.api import ManagedSerializer from authentik.managed.api import ManagedSerializer
@ -64,7 +64,7 @@ class TokenViewSet(ModelViewSet):
ordering = ["expires"] ordering = ["expires"]
def perform_create(self, serializer: TokenSerializer): def perform_create(self, serializer: TokenSerializer):
serializer.save(user=self.request.user) serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
@permission_required("authentik_core.view_token_key") @permission_required("authentik_core.view_token_key")
@swagger_auto_schema( @swagger_auto_schema(

View File

@ -1,10 +1,11 @@
"""User API Views""" """User API Views"""
from django.http.response import Http404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
from guardian.utils import get_anonymous_user from guardian.utils import get_anonymous_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, SerializerMethodField from rest_framework.fields import CharField, JSONField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import BooleanField, ModelSerializer from rest_framework.serializers import BooleanField, ModelSerializer
@ -12,13 +13,14 @@ from rest_framework.viewsets import ModelViewSet
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.api.decorators import permission_required from authentik.api.decorators import permission_required
from authentik.core.api.utils import LinkSerializer, PassiveSerializer from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import ( from authentik.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_ORIGINAL_USER,
SESSION_IMPERSONATE_USER, SESSION_IMPERSONATE_USER,
) )
from authentik.core.models import Token, TokenIntents, User from authentik.core.models import Token, TokenIntents, User
from authentik.events.models import EventAction from authentik.events.models import EventAction
from authentik.flows.models import Flow, FlowDesignation
class UserSerializer(ModelSerializer): class UserSerializer(ModelSerializer):
@ -26,6 +28,7 @@ class UserSerializer(ModelSerializer):
is_superuser = BooleanField(read_only=True) is_superuser = BooleanField(read_only=True)
avatar = CharField(read_only=True) avatar = CharField(read_only=True)
attributes = JSONField(validators=[is_dict], required=False)
class Meta: class Meta:
@ -120,12 +123,16 @@ class UserViewSet(ModelViewSet):
@permission_required("authentik_core.reset_user_password") @permission_required("authentik_core.reset_user_password")
@swagger_auto_schema( @swagger_auto_schema(
responses={"200": LinkSerializer(many=False)}, responses={"200": LinkSerializer(many=False), "404": "No recovery flow found."},
) )
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name, unused-argument # pylint: disable=invalid-name, unused-argument
def recovery(self, request: Request, pk: int) -> Response: def recovery(self, request: Request, pk: int) -> Response:
"""Create a temporary link that a user can use to recover their accounts""" """Create a temporary link that a user can use to recover their accounts"""
# Check that there is a recovery flow, if not return an error
flow = Flow.with_policy(request, designation=FlowDesignation.RECOVERY)
if not flow:
raise Http404
user: User = self.get_object() user: User = self.get_object()
token, __ = Token.objects.get_or_create( token, __ = Token.objects.get_or_create(
identifier=f"{user.uid}-password-reset", identifier=f"{user.uid}-password-reset",

View File

@ -1,7 +1,20 @@
"""API Utilities""" """API Utilities"""
from typing import Any
from django.db.models import Model from django.db.models import Model
from rest_framework.fields import CharField, IntegerField from rest_framework.fields import CharField, IntegerField
from rest_framework.serializers import Serializer, SerializerMethodField from rest_framework.serializers import (
Serializer,
SerializerMethodField,
ValidationError,
)
def is_dict(value: Any):
"""Ensure a value is a dictionary, useful for JSONFields"""
if isinstance(value, dict):
return
raise ValidationError("Value must be a dictionary.")
class PassiveSerializer(Serializer): class PassiveSerializer(Serializer):
@ -35,6 +48,7 @@ class TypeCreateSerializer(PassiveSerializer):
name = CharField(required=True) name = CharField(required=True)
description = CharField(required=True) description = CharField(required=True)
component = CharField(required=True) component = CharField(required=True)
model_name = CharField(required=True)
class CacheSerializer(PassiveSerializer): class CacheSerializer(PassiveSerializer):

View File

@ -1,6 +1,7 @@
"""Channels base classes""" """Channels base classes"""
from channels.exceptions import DenyConnection from channels.exceptions import DenyConnection
from channels.generic.websocket import JsonWebsocketConsumer from channels.generic.websocket import JsonWebsocketConsumer
from rest_framework.exceptions import AuthenticationFailed
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.api.auth import token_from_header from authentik.api.auth import token_from_header
@ -22,9 +23,13 @@ class AuthJsonConsumer(JsonWebsocketConsumer):
raw_header = headers[b"authorization"] raw_header = headers[b"authorization"]
token = token_from_header(raw_header) try:
if not token: token = token_from_header(raw_header)
LOGGER.warning("Failed to authenticate") # token is only None when no header was given, in which case we deny too
if not token:
raise DenyConnection()
except AuthenticationFailed as exc:
LOGGER.warning("Failed to authenticate", exc=exc)
raise DenyConnection() raise DenyConnection()
self.user = token.user self.user = token.user

View File

@ -10,9 +10,13 @@
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title> <title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}"> <link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}"> <link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}?v={{ ak_version }}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/page.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/empty-state.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/spinner.css' %}?v={{ ak_version }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}"> <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}">
<script src="{% static 'dist/poly.js' %}?v={{ ak_version }}" type="module"></script> <script src="{% static 'dist/poly.js' %}?v={{ ak_version }}" type="module"></script>
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
{% block head %} {% block head %}
{% endblock %} {% endblock %}
</head> </head>

View File

@ -10,7 +10,7 @@
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-admin> <ak-interface-admin>
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;"> <div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}"> <span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">

View File

@ -10,7 +10,7 @@
{% block body %} {% block body %}
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-flow-executor> <ak-flow-executor>
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;"> <div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}"> <span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">

View File

@ -0,0 +1,15 @@
"""Test API Utils"""
from rest_framework.exceptions import ValidationError
from rest_framework.test import APITestCase
from authentik.core.api.utils import is_dict
class TestAPIUtils(APITestCase):
"""Test API Utils"""
def test_is_dict(self):
"""Test is_dict"""
self.assertIsNone(is_dict({}))
with self.assertRaises(ValidationError):
is_dict("foo")

View File

@ -2,7 +2,7 @@
from django.urls.base import reverse from django.urls.base import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import Token, User from authentik.core.models import Token, TokenIntents, User
class TestTokenAPI(APITestCase): class TestTokenAPI(APITestCase):
@ -19,4 +19,6 @@ class TestTokenAPI(APITestCase):
reverse("authentik_api:token-list"), {"identifier": "test-token"} reverse("authentik_api:token-list"), {"identifier": "test-token"}
) )
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(Token.objects.get(identifier="test-token").user, self.user) token = Token.objects.get(identifier="test-token")
self.assertEqual(token.user, self.user)
self.assertEqual(token.intent, TokenIntents.INTENT_API)

View File

@ -153,10 +153,6 @@ class EventViewSet(ReadOnlyModelViewSet):
data = [] data = []
for value, name in EventAction.choices: for value, name in EventAction.choices:
data.append( data.append(
{ {"name": name, "description": "", "component": value, "model_name": ""}
"name": name,
"description": "",
"component": value,
}
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -4,6 +4,7 @@ from typing import Iterable
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework import mixins from rest_framework import mixins
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.serializers import ModelSerializer, SerializerMethodField
@ -19,6 +20,12 @@ from authentik.lib.utils.reflection import all_subclasses
LOGGER = get_logger() LOGGER = get_logger()
class StageUserSettingSerializer(UserSettingSerializer):
"""User settings but can include a configure flow"""
configure_flow = BooleanField(required=False)
class StageSerializer(ModelSerializer, MetaNameSerializer): class StageSerializer(ModelSerializer, MetaNameSerializer):
"""Stage Serializer""" """Stage Serializer"""
@ -73,12 +80,13 @@ class StageViewSet(
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name,
} }
) )
data = sorted(data, key=lambda x: x["name"]) data = sorted(data, key=lambda x: x["name"])
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: UserSettingSerializer(many=True)}) @swagger_auto_schema(responses={200: StageUserSettingSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response: def user_settings(self, request: Request) -> Response:
"""Get all stages the user can configure""" """Get all stages the user can configure"""
@ -89,6 +97,10 @@ class StageViewSet(
if not user_settings: if not user_settings:
continue continue
user_settings.initial_data["object_uid"] = str(stage.pk) user_settings.initial_data["object_uid"] = str(stage.pk)
if hasattr(stage, "configure_flow"):
user_settings.initial_data["configure_flow"] = bool(
stage.configure_flow
)
if not user_settings.is_valid(): if not user_settings.is_valid():
LOGGER.warning(user_settings.errors) LOGGER.warning(user_settings.errors)
matching_stages.append(user_settings.initial_data) matching_stages.append(user_settings.initial_data)

View File

@ -127,6 +127,7 @@ class FlowExecutorView(APIView):
@swagger_auto_schema( @swagger_auto_schema(
responses={ responses={
200: Challenge(), 200: Challenge(),
404: "No Token found", # This error can be raised by the email stage
}, },
request_body=no_body, request_body=no_body,
manual_parameters=[ manual_parameters=[

View File

@ -82,6 +82,7 @@ class ServiceConnectionViewSet(
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name,
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -8,14 +8,14 @@ from rest_framework.serializers import JSONField, ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.outposts.models import Outpost, default_outpost_config from authentik.outposts.models import Outpost, default_outpost_config
class OutpostSerializer(ModelSerializer): class OutpostSerializer(ModelSerializer):
"""Outpost Serializer""" """Outpost Serializer"""
_config = JSONField() _config = JSONField(validators=[is_dict])
providers_obj = ProviderSerializer(source="providers", many=True, read_only=True) providers_obj = ProviderSerializer(source="providers", many=True, read_only=True)
class Meta: class Meta:

View File

@ -356,7 +356,7 @@ class Outpost(models.Model):
intent=TokenIntents.INTENT_API, intent=TokenIntents.INTENT_API,
description=f"Autogenerated by authentik for Outpost {self.name}", description=f"Autogenerated by authentik for Outpost {self.name}",
expiring=False, expiring=False,
managed="goauthentik.io/outpost", managed=f"goauthentik.io/outpost/{self.token_identifier}",
) )
def get_required_objects(self) -> Iterable[models.Model]: def get_required_objects(self) -> Iterable[models.Model]:

View File

@ -67,6 +67,8 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str):
outpost: Outpost = Outpost.objects.get(pk=outpost_pk) outpost: Outpost = Outpost.objects.get(pk=outpost_pk)
self.set_uid(slugify(outpost.name)) self.set_uid(slugify(outpost.name))
try: try:
if not outpost.service_connection:
return
if outpost.type == OutpostType.PROXY: if outpost.type == OutpostType.PROXY:
service_connection = outpost.service_connection service_connection = outpost.service_connection
if isinstance(service_connection, DockerServiceConnection): if isinstance(service_connection, DockerServiceConnection):

View File

@ -2,7 +2,7 @@
from rest_framework.fields import BooleanField, CharField, JSONField, ListField from rest_framework.fields import BooleanField, CharField, JSONField, ListField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.core.models import User from authentik.core.models import User
@ -10,7 +10,7 @@ class PolicyTestSerializer(PassiveSerializer):
"""Test policy execution for a user with context""" """Test policy execution for a user with context"""
user = PrimaryKeyRelatedField(queryset=User.objects.all()) user = PrimaryKeyRelatedField(queryset=User.objects.all())
context = JSONField(required=False) context = JSONField(required=False, validators=[is_dict])
class PolicyTestResultSerializer(PassiveSerializer): class PolicyTestResultSerializer(PassiveSerializer):

View File

@ -108,6 +108,7 @@ class PolicyViewSet(
"name": subclass._meta.verbose_name, "name": subclass._meta.verbose_name,
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name,
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -1,13 +1,23 @@
"""Test authorize view""" """Test authorize view"""
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application, User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.providers.oauth2.errors import ( from authentik.providers.oauth2.errors import (
AuthorizeError, AuthorizeError,
ClientIdError, ClientIdError,
RedirectUriError, RedirectUriError,
) )
from authentik.providers.oauth2.models import OAuth2Provider from authentik.providers.oauth2.generators import generate_client_id
from authentik.providers.oauth2.models import (
AuthorizationCode,
GrantTypes,
OAuth2Provider,
RefreshToken,
)
from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams
@ -32,15 +42,194 @@ class TestViewsAuthorize(TestCase):
) )
OAuthAuthorizationParams.from_request(request) OAuthAuthorizationParams.from_request(request)
def test_missing_redirect_uri(self): def test_request(self):
"""test missing redirect URI""" """test request param"""
OAuth2Provider.objects.create( OAuth2Provider.objects.create(
name="test", name="test",
client_id="test", client_id="test",
authorization_flow=Flow.objects.first(), authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
with self.assertRaises(AuthorizeError):
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://local.invalid",
"request": "foo",
},
)
OAuthAuthorizationParams.from_request(request)
def test_redirect_uri(self):
"""test missing/invalid redirect URI"""
OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
) )
with self.assertRaises(RedirectUriError): with self.assertRaises(RedirectUriError):
request = self.factory.get( request = self.factory.get(
"/", data={"response_type": "code", "client_id": "test"} "/", data={"response_type": "code", "client_id": "test"}
) )
OAuthAuthorizationParams.from_request(request) OAuthAuthorizationParams.from_request(request)
with self.assertRaises(RedirectUriError):
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(
name="test",
client_id="test",
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
request = self.factory.get(
"/",
data={
"response_type": "code",
"client_id": "test",
"redirect_uri": "http://local.invalid",
},
)
self.assertEqual(
OAuthAuthorizationParams.from_request(request).grant_type,
GrantTypes.AUTHORIZATION_CODE,
)
request = self.factory.get(
"/",
data={
"response_type": "id_token",
"client_id": "test",
"redirect_uri": "http://local.invalid",
"scope": "openid",
"state": "foo",
},
)
self.assertEqual(
OAuthAuthorizationParams.from_request(request).grant_type,
GrantTypes.IMPLICIT,
)
# Implicit without openid scope
with self.assertRaises(AuthorizeError):
request = self.factory.get(
"/",
data={
"response_type": "id_token",
"client_id": "test",
"redirect_uri": "http://local.invalid",
"state": "foo",
},
)
self.assertEqual(
OAuthAuthorizationParams.from_request(request).grant_type,
GrantTypes.IMPLICIT,
)
request = self.factory.get(
"/",
data={
"response_type": "code token",
"client_id": "test",
"redirect_uri": "http://local.invalid",
"scope": "openid",
"state": "foo",
},
)
self.assertEqual(
OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID
)
with self.assertRaises(AuthorizeError):
request = self.factory.get(
"/",
data={
"response_type": "invalid",
"client_id": "test",
"redirect_uri": "http://local.invalid",
},
)
OAuthAuthorizationParams.from_request(request)
def test_full_code(self):
"""Test full authorization"""
flow = Flow.objects.create(slug="empty")
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=flow,
redirect_uris="http://localhost",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_client_id()
user = User.objects.get(username="akadmin")
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "http://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.REDIRECT.value,
"to": f"http://localhost?code={code.code}&state={state}",
},
)
def test_full_implicit(self):
"""Test full authorization"""
flow = Flow.objects.create(slug="empty")
provider = OAuth2Provider.objects.create(
name="test",
client_id="test",
authorization_flow=flow,
redirect_uris="http://localhost",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_client_id()
user = User.objects.get(username="akadmin")
self.client.force_login(user)
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "id_token",
"client_id": "test",
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
token: RefreshToken = RefreshToken.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.REDIRECT.value,
"to": (
f"http://localhost#access_token={token.access_token}"
f"&id_token={provider.encode(token.id_token.to_dict())}&token_type=bearer"
f"&expires_in=600&state={state}"
),
},
)

View File

@ -0,0 +1,171 @@
"""Test token view"""
from base64 import b64encode
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import User
from authentik.flows.models import Flow
from authentik.providers.oauth2.constants import (
GRANT_TYPE_AUTHORIZATION_CODE,
GRANT_TYPE_REFRESH_TOKEN,
)
from authentik.providers.oauth2.generators import (
generate_client_id,
generate_client_secret,
)
from authentik.providers.oauth2.models import (
AuthorizationCode,
OAuth2Provider,
RefreshToken,
)
from authentik.providers.oauth2.views.token import TokenParams
class TestViewsToken(TestCase):
"""Test token view"""
def setUp(self) -> None:
super().setUp()
self.factory = RequestFactory()
def test_request_auth_code(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
).decode()
user = User.objects.get(username="akadmin")
code = AuthorizationCode.objects.create(
code="foobar", provider=provider, user=user
)
request = self.factory.post(
"/",
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
params = TokenParams.from_request(request)
self.assertEqual(params.provider, provider)
def test_request_refresh_token(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
).decode()
user = User.objects.get(username="akadmin")
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
)
request = self.factory.post(
"/",
data={
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
"refresh_token": token.refresh_token,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
params = TokenParams.from_request(request)
self.assertEqual(params.provider, provider)
def test_auth_code_view(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
).decode()
user = User.objects.get(username="akadmin")
code = AuthorizationCode.objects.create(
code="foobar", provider=provider, user=user
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
new_token: RefreshToken = RefreshToken.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
{
"access_token": new_token.access_token,
"refresh_token": new_token.refresh_token,
"token_type": "bearer",
"expires_in": 600,
"id_token": provider.encode(
new_token.id_token.to_dict(),
),
},
)
def test_refresh_token_view(self):
"""test request param"""
provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="http://local.invalid",
)
header = b64encode(
f"{provider.client_id}:{provider.client_secret}".encode()
).decode()
user = User.objects.get(username="akadmin")
token: RefreshToken = RefreshToken.objects.create(
provider=provider,
user=user,
refresh_token=generate_client_id(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
"refresh_token": token.refresh_token,
"redirect_uri": "http://local.invalid",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
new_token: RefreshToken = (
RefreshToken.objects.filter(user=user).exclude(pk=token.pk).first()
)
self.assertJSONEqual(
force_str(response.content),
{
"access_token": new_token.access_token,
"refresh_token": new_token.refresh_token,
"token_type": "bearer",
"expires_in": 600,
"id_token": provider.encode(
new_token.id_token.to_dict(),
),
},
)

View File

@ -3,6 +3,7 @@ import re
from base64 import b64decode from base64 import b64decode
from binascii import Error from binascii import Error
from typing import Optional from typing import Optional
from urllib.parse import urlparse
from django.http import HttpRequest, HttpResponse, JsonResponse from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
@ -25,15 +26,34 @@ class TokenResponse(JsonResponse):
self["Pragma"] = "no-cache" self["Pragma"] = "no-cache"
def cors_allow_any(request, response): def cors_allow_any(request: HttpRequest, response: HttpResponse, *allowed_origins: str):
""" """Add headers to permit CORS requests from any origin, with or without credentials,
Add headers to permit CORS requests from any origin, with or without credentials, with any headers."""
with any headers.
"""
origin = request.META.get("HTTP_ORIGIN") origin = request.META.get("HTTP_ORIGIN")
if not origin: if not origin:
return response return response
# OPTIONS requests don't have an authorization header -> hence
# we can't extract the provider this request is for
# so for options requests we allow the calling origin without checking
allowed = request.method == "OPTIONS"
received_origin = urlparse(origin)
for allowed_origin in allowed_origins:
url = urlparse(allowed_origin)
if (
received_origin.scheme == url.scheme
and received_origin.hostname == url.hostname
and received_origin.port == url.port
):
allowed = True
if not allowed:
LOGGER.warning(
"CORS: Origin is not an allowed origin",
requested=origin,
allowed=allowed_origins,
)
return response
# From the CORS spec: The string "*" cannot be used for a resource that supports credentials. # From the CORS spec: The string "*" cannot be used for a resource that supports credentials.
response["Access-Control-Allow-Origin"] = origin response["Access-Control-Allow-Origin"] = origin
patch_vary_headers(response, ["Origin"]) patch_vary_headers(response, ["Origin"])

View File

@ -6,7 +6,7 @@ from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
from uuid import uuid4 from uuid import uuid4
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -236,6 +236,9 @@ class OAuthFulfillmentStage(StageView):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""final Stage of an OAuth2 Flow""" """final Stage of an OAuth2 Flow"""
if PLAN_CONTEXT_PARAMS not in self.executor.plan.context:
LOGGER.warning("Got to fulfillment stage with no pending context")
return HttpResponseBadRequest()
self.params: OAuthAuthorizationParams = self.executor.plan.context.pop( self.params: OAuthAuthorizationParams = self.executor.plan.context.pop(
PLAN_CONTEXT_PARAMS PLAN_CONTEXT_PARAMS
) )

View File

@ -30,6 +30,8 @@ PLAN_CONTEXT_SCOPES = "scopes"
class ProviderInfoView(View): class ProviderInfoView(View):
"""OpenID-compliant Provider Info""" """OpenID-compliant Provider Info"""
provider: OAuth2Provider
def get_info(self, provider: OAuth2Provider) -> dict[str, Any]: def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
"""Get dictionary for OpenID Connect information""" """Get dictionary for OpenID Connect information"""
scopes = list( scopes = list(
@ -95,19 +97,20 @@ class ProviderInfoView(View):
} }
# pylint: disable=unused-argument # pylint: disable=unused-argument
def get( def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
self, request: HttpRequest, application_slug: str, *args, **kwargs
) -> HttpResponse:
"""OpenID-compliant Provider Info""" """OpenID-compliant Provider Info"""
return JsonResponse(
self.get_info(self.provider), json_dumps_params={"indent": 2}
)
def dispatch(
self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
) -> HttpResponse:
# Since this view only supports get, we can statically set the CORS headers
application = get_object_or_404(Application, slug=application_slug) application = get_object_or_404(Application, slug=application_slug)
provider: OAuth2Provider = get_object_or_404( self.provider: OAuth2Provider = get_object_or_404(
OAuth2Provider, pk=application.provider_id OAuth2Provider, pk=application.provider_id
) )
return JsonResponse(self.get_info(provider), json_dumps_params={"indent": 2})
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
# Since this view only supports get, we can statically set the CORS headers
response = super().dispatch(request, *args, **kwargs) response = super().dispatch(request, *args, **kwargs)
cors_allow_any(request, response) cors_allow_any(request, response, *self.provider.redirect_uris.split("\n"))
return response return response

View File

@ -198,7 +198,7 @@ class TokenView(View):
response_dict = { response_dict = {
"access_token": refresh_token.access_token, "access_token": refresh_token.access_token,
"refresh_token": refresh_token.refresh_token, "refresh_token": refresh_token.refresh_token,
"token_type": "Bearer", "token_type": "bearer",
"expires_in": timedelta_from_string( "expires_in": timedelta_from_string(
self.params.provider.token_validity self.params.provider.token_validity
).seconds, ).seconds,

View File

@ -1,7 +1,8 @@
"""authentik OAuth2 OpenID Userinfo views""" """authentik OAuth2 OpenID Userinfo views"""
from typing import Any from typing import Any, Optional
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -22,6 +23,8 @@ class UserInfoView(View):
"""Create a dictionary with all the requested claims about the End-User. """Create a dictionary with all the requested claims about the End-User.
See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse""" See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse"""
token: Optional[RefreshToken]
def get_scope_descriptions(self, scopes: list[str]) -> list[dict[str, str]]: def get_scope_descriptions(self, scopes: list[str]) -> list[dict[str, str]]:
"""Get a list of all Scopes's descriptions""" """Get a list of all Scopes's descriptions"""
scope_descriptions = [] scope_descriptions = []
@ -79,16 +82,25 @@ class UserInfoView(View):
final_claims.update(value) final_claims.update(value)
return final_claims return final_claims
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
self.token = kwargs.get("token", None)
response = super().dispatch(request, *args, **kwargs)
allowed_origins = []
if self.token:
allowed_origins = self.token.provider.redirect_uris.split("\n")
cors_allow_any(self.request, response, *allowed_origins)
return response
def options(self, request: HttpRequest) -> HttpResponse: def options(self, request: HttpRequest) -> HttpResponse:
return cors_allow_any(self.request, TokenResponse({})) return TokenResponse({})
def get(self, request: HttpRequest, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, **kwargs) -> HttpResponse:
"""Handle GET Requests for UserInfo""" """Handle GET Requests for UserInfo"""
token: RefreshToken = kwargs["token"] if not self.token:
claims = self.get_claims(token) return HttpResponseBadRequest()
claims["sub"] = token.id_token.sub claims = self.get_claims(self.token)
claims["sub"] = self.token.id_token.sub
response = TokenResponse(claims) response = TokenResponse(claims)
cors_allow_any(self.request, response)
return response return response
def post(self, request: HttpRequest, **kwargs) -> HttpResponse: def post(self, request: HttpRequest, **kwargs) -> HttpResponse:

View File

@ -21,7 +21,7 @@ class ProxyScopeMappingManager(ObjectManager):
EnsureExists( EnsureExists(
ScopeMapping, ScopeMapping,
"goauthentik.io/providers/proxy/scope-proxy", "goauthentik.io/providers/proxy/scope-proxy",
name="authentik default OAuth Mapping: proxy outpost", name="authentik default OAuth Mapping: Proxy outpost",
scope_name=SCOPE_AK_PROXY, scope_name=SCOPE_AK_PROXY,
expression=SCOPE_AK_PROXY_EXPRESSION, expression=SCOPE_AK_PROXY_EXPRESSION,
), ),

View File

@ -1,13 +1,12 @@
"""Channels Messages storage""" """Channels Messages storage"""
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.contrib.messages.storage.base import Message from django.contrib.messages.storage.base import BaseStorage, Message
from django.contrib.messages.storage.fallback import FallbackStorage
from django.core.cache import cache from django.core.cache import cache
from django.http.request import HttpRequest from django.http.request import HttpRequest
class ChannelsStorage(FallbackStorage): class ChannelsStorage(BaseStorage):
"""Send contrib.messages over websocket""" """Send contrib.messages over websocket"""
def __init__(self, request: HttpRequest) -> None: def __init__(self, request: HttpRequest) -> None:
@ -15,6 +14,9 @@ class ChannelsStorage(FallbackStorage):
super().__init__(request) super().__init__(request)
self.channel = get_channel_layer() self.channel = get_channel_layer()
def _get(self):
return [], True
def _store(self, messages: list[Message], response, *args, **kwargs): def _store(self, messages: list[Message], response, *args, **kwargs):
prefix = f"user_{self.request.session.session_key}_messages_" prefix = f"user_{self.request.session.session_key}_messages_"
keys = cache.keys(f"{prefix}*") keys = cache.keys(f"{prefix}*")

View File

@ -143,7 +143,7 @@ SWAGGER_SETTINGS = {
"authentik.api.pagination_schema.PaginationInspector", "authentik.api.pagination_schema.PaginationInspector",
], ],
"SECURITY_DEFINITIONS": { "SECURITY_DEFINITIONS": {
"token": {"type": "apiKey", "name": "Authorization", "in": "header"} "Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}
}, },
} }
@ -254,8 +254,8 @@ EMAIL_HOST = CONFIG.y("email.host")
EMAIL_PORT = int(CONFIG.y("email.port")) EMAIL_PORT = int(CONFIG.y("email.port"))
EMAIL_HOST_USER = CONFIG.y("email.username") EMAIL_HOST_USER = CONFIG.y("email.username")
EMAIL_HOST_PASSWORD = CONFIG.y("email.password") EMAIL_HOST_PASSWORD = CONFIG.y("email.password")
EMAIL_USE_TLS = CONFIG.y("email.use_tls") EMAIL_USE_TLS = CONFIG.y_bool("email.use_tls", True)
EMAIL_USE_SSL = CONFIG.y("email.use_ssl") EMAIL_USE_SSL = CONFIG.y_bool("email.use_ssl", False)
EMAIL_TIMEOUT = int(CONFIG.y("email.timeout")) EMAIL_TIMEOUT = int(CONFIG.y("email.timeout"))
DEFAULT_FROM_EMAIL = CONFIG.y("email.from") DEFAULT_FROM_EMAIL = CONFIG.y("email.from")
SERVER_EMAIL = DEFAULT_FROM_EMAIL SERVER_EMAIL = DEFAULT_FROM_EMAIL
@ -338,6 +338,7 @@ if CONFIG.y("postgresql.s3_backup"):
# Sentry integration # Sentry integration
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False) _ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
if not DEBUG and _ERROR_REPORTING: if not DEBUG and _ERROR_REPORTING:
# pylint: disable=abstract-class-instantiated
sentry_init( sentry_init(
dsn="https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", dsn="https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
integrations=[ integrations=[

View File

@ -1,9 +1,11 @@
"""Sync LDAP Users and groups into authentik""" """Sync LDAP Users and groups into authentik"""
import ldap3 import ldap3
import ldap3.core.exceptions import ldap3.core.exceptions
from django.core.exceptions import FieldError
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from authentik.core.models import Group from authentik.core.models import Group
from authentik.events.models import Event, EventAction
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
@ -47,14 +49,17 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
"defaults": defaults, "defaults": defaults,
} }
) )
except IntegrityError as exc: except (IntegrityError, FieldError) as exc:
self._logger.warning("Failed to create group", exc=exc) Event.new(
self._logger.warning( EventAction.CONFIGURATION_ERROR,
( message=(
"To merge new group with existing group, set the group's " f"Failed to create group: {str(exc)} "
"To merge new group with existing group, set the groups's "
f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'" f"Attribute '{LDAP_UNIQUENESS}' to '{uniq}'"
) ),
) source=self._source,
dn=group_dn,
).save()
else: else:
self._logger.debug("Synced group", group=ak_group.name, created=created) self._logger.debug("Synced group", group=ak_group.name, created=created)
group_count += 1 group_count += 1

View File

@ -3,6 +3,7 @@ from datetime import datetime
import ldap3 import ldap3
import ldap3.core.exceptions import ldap3.core.exceptions
from django.core.exceptions import FieldError
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from pytz import UTC from pytz import UTC
@ -48,7 +49,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
"defaults": defaults, "defaults": defaults,
} }
) )
except IntegrityError as exc: except (IntegrityError, FieldError) as exc:
Event.new( Event.new(
EventAction.CONFIGURATION_ERROR, EventAction.CONFIGURATION_ERROR,
message=( message=(

View File

@ -5,6 +5,7 @@ from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
@ -47,6 +48,20 @@ class OAuthSourceSerializer(SourceSerializer):
"""Get source's type configuration""" """Get source's type configuration"""
return SourceTypeSerializer(instace.type).data return SourceTypeSerializer(instace.type).data
def validate(self, attrs: dict) -> dict:
provider_type = MANAGER.find_type(attrs.get("provider_type", ""))
for url in [
"authorization_url",
"access_token_url",
"profile_url",
]:
if getattr(provider_type, url, None) is None:
if url not in attrs:
raise ValidationError(
f"{url} is required for provider {provider_type.name}"
)
return attrs
class Meta: class Meta:
model = OAuthSource model = OAuthSource
fields = SourceSerializer.Meta.fields + [ fields = SourceSerializer.Meta.fields + [

View File

@ -9,6 +9,7 @@ from requests.models import Response
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__ from authentik import __version__
from authentik.events.models import Event, EventAction
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
LOGGER = get_logger() LOGGER = get_logger()
@ -59,7 +60,16 @@ class BaseOAuthClient:
args.update(additional) args.update(additional)
params = urlencode(args) params = urlencode(args)
LOGGER.info("redirect args", **args) LOGGER.info("redirect args", **args)
return f"{self.source.authorization_url}?{params}" base_url = self.source.type.authorization_url
if self.source.authorization_url:
base_url = self.source.authorization_url
if base_url == "":
Event.new(
EventAction.CONFIGURATION_ERROR,
source=self.source,
message="Source has an empty authorization URL.",
).save()
return f"{base_url}?{params}"
def parse_raw_token(self, raw_token: str) -> dict[str, Any]: def parse_raw_token(self, raw_token: str) -> dict[str, Any]:
"Parse token and secret from raw token response." "Parse token and secret from raw token response."

View File

@ -28,9 +28,12 @@ class OAuthClient(BaseOAuthClient):
if raw_token is not None and verifier is not None: if raw_token is not None and verifier is not None:
token = self.parse_raw_token(raw_token) token = self.parse_raw_token(raw_token)
try: try:
access_token_url: str = self.source.type.access_token_url or ""
if self.source.access_token_url:
access_token_url = self.source.access_token_url
response = self.do_request( response = self.do_request(
"post", "post",
self.source.access_token_url, access_token_url,
token=token, token=token,
headers=self._default_headers, headers=self._default_headers,
oauth_verifier=verifier, oauth_verifier=verifier,
@ -48,9 +51,12 @@ class OAuthClient(BaseOAuthClient):
"Fetch the OAuth request token. Only required for OAuth 1.0." "Fetch the OAuth request token. Only required for OAuth 1.0."
callback = self.request.build_absolute_uri(self.callback) callback = self.request.build_absolute_uri(self.callback)
try: try:
request_token_url: str = self.source.type.request_token_url or ""
if self.source.request_token_url:
request_token_url = self.source.request_token_url
response = self.do_request( response = self.do_request(
"post", "post",
self.source.request_token_url, request_token_url,
headers=self._default_headers, headers=self._default_headers,
oauth_callback=callback, oauth_callback=callback,
) )

View File

@ -56,9 +56,12 @@ class OAuth2Client(BaseOAuthClient):
LOGGER.warning("No code returned by the source") LOGGER.warning("No code returned by the source")
return None return None
try: try:
access_token_url = self.source.type.access_token_url or ""
if self.source.access_token_url:
access_token_url = self.source.access_token_url
response = self.session.request( response = self.session.request(
"post", "post",
self.source.access_token_url, access_token_url,
data=args, data=args,
headers=self._default_headers, headers=self._default_headers,
) )

View File

@ -0,0 +1,43 @@
# Generated by Django 3.2 on 2021-04-16 07:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_oauth", "0002_auto_20200520_1108"),
]
operations = [
migrations.AlterField(
model_name="oauthsource",
name="access_token_url",
field=models.CharField(
blank=True,
help_text="URL used by authentik to retrive tokens.",
max_length=255,
verbose_name="Access Token URL",
),
),
migrations.AlterField(
model_name="oauthsource",
name="authorization_url",
field=models.CharField(
blank=True,
help_text="URL the user is redirect to to conest the flow.",
max_length=255,
verbose_name="Authorization URL",
),
),
migrations.AlterField(
model_name="oauthsource",
name="profile_url",
field=models.CharField(
blank=True,
help_text="URL used by authentik to get user information.",
max_length=255,
verbose_name="Profile URL",
),
),
]

View File

@ -0,0 +1,79 @@
# Generated by Django 3.2 on 2021-04-17 19:00
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def update_empty_urls(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
OAuthSource = apps.get_model("authentik_sources_oauth", "oauthsource")
db_alias = schema_editor.connection.alias
for source in OAuthSource.objects.using(db_alias).all():
changed = False
if source.access_token_url == "":
source.access_token_url = None
changed = True
if source.authorization_url == "":
source.authorization_url = None
changed = True
if source.profile_url == "":
source.profile_url = None
changed = True
if source.request_token_url == "":
source.request_token_url = None
changed = True
if changed:
source.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_oauth", "0003_auto_20210416_0726"),
]
operations = [
migrations.AlterField(
model_name="oauthsource",
name="access_token_url",
field=models.CharField(
help_text="URL used by authentik to retrive tokens.",
max_length=255,
null=True,
verbose_name="Access Token URL",
),
),
migrations.AlterField(
model_name="oauthsource",
name="authorization_url",
field=models.CharField(
help_text="URL the user is redirect to to conest the flow.",
max_length=255,
null=True,
verbose_name="Authorization URL",
),
),
migrations.AlterField(
model_name="oauthsource",
name="profile_url",
field=models.CharField(
help_text="URL used by authentik to get user information.",
max_length=255,
null=True,
verbose_name="Profile URL",
),
),
migrations.AlterField(
model_name="oauthsource",
name="request_token_url",
field=models.CharField(
help_text="URL used to request the initial token. This URL is only required for OAuth 1.",
max_length=255,
null=True,
verbose_name="Request Token URL",
),
),
migrations.RunPython(update_empty_urls),
]

View File

@ -19,7 +19,7 @@ class OAuthSource(Source):
provider_type = models.CharField(max_length=255) provider_type = models.CharField(max_length=255)
request_token_url = models.CharField( request_token_url = models.CharField(
blank=True, null=True,
max_length=255, max_length=255,
verbose_name=_("Request Token URL"), verbose_name=_("Request Token URL"),
help_text=_( help_text=_(
@ -28,16 +28,19 @@ class OAuthSource(Source):
) )
authorization_url = models.CharField( authorization_url = models.CharField(
max_length=255, max_length=255,
null=True,
verbose_name=_("Authorization URL"), verbose_name=_("Authorization URL"),
help_text=_("URL the user is redirect to to conest the flow."), help_text=_("URL the user is redirect to to conest the flow."),
) )
access_token_url = models.CharField( access_token_url = models.CharField(
max_length=255, max_length=255,
null=True,
verbose_name=_("Access Token URL"), verbose_name=_("Access Token URL"),
help_text=_("URL used by authentik to retrive tokens."), help_text=_("URL used by authentik to retrive tokens."),
) )
profile_url = models.CharField( profile_url = models.CharField(
max_length=255, max_length=255,
null=True,
verbose_name=_("Profile URL"), verbose_name=_("Profile URL"),
help_text=_("URL used by authentik to get user information."), help_text=_("URL used by authentik to get user information."),
) )
@ -49,7 +52,7 @@ class OAuthSource(Source):
"""Return the provider instance for this source""" """Return the provider instance for this source"""
from authentik.sources.oauth.types.manager import MANAGER from authentik.sources.oauth.types.manager import MANAGER
return MANAGER.find_type(self) return MANAGER.find_type(self.provider_type)
@property @property
def component(self) -> str: def component(self) -> str:
@ -160,6 +163,16 @@ class OpenIDOAuthSource(OAuthSource):
verbose_name_plural = _("OpenID OAuth Sources") verbose_name_plural = _("OpenID OAuth Sources")
class PlexOAuthSource(OAuthSource):
"""Login using plex.tv."""
class Meta:
abstract = True
verbose_name = _("Plex OAuth Source")
verbose_name_plural = _("Plex OAuth Sources")
class UserOAuthSourceConnection(UserSourceConnection): class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider.""" """Authorized remote OAuth provider."""

View File

@ -9,4 +9,5 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.twitter", "authentik.sources.oauth.types.twitter",
"authentik.sources.oauth.types.azure_ad", "authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.oidc", "authentik.sources.oauth.types.oidc",
"authentik.sources.oauth.types.plex",
] ]

View File

@ -2,6 +2,7 @@
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from authentik.sources.oauth.api.source import OAuthSourceSerializer
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
@ -18,6 +19,31 @@ class TestOAuthSource(TestCase):
consumer_key="", consumer_key="",
) )
def test_api_validate(self):
"""Test API validation"""
self.assertTrue(
OAuthSourceSerializer(
data={
"name": "foo",
"slug": "bar",
"provider_type": "google",
"consumer_key": "foo",
"consumer_secret": "foo",
}
).is_valid()
)
self.assertFalse(
OAuthSourceSerializer(
data={
"name": "foo",
"slug": "bar",
"provider_type": "openid-connect",
"consumer_key": "foo",
"consumer_secret": "foo",
}
).is_valid()
)
def test_source_redirect(self): def test_source_redirect(self):
"""test redirect view""" """test redirect view"""
self.client.get( self.client.get(

View File

@ -1,6 +1,6 @@
"""Source type manager""" """Source type manager"""
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Callable, Optional from typing import Callable, Optional
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -9,9 +9,6 @@ from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger() LOGGER = get_logger()
if TYPE_CHECKING:
from authentik.sources.oauth.models import OAuthSource
class RequestKind(Enum): class RequestKind(Enum):
"""Enum of OAuth Request types""" """Enum of OAuth Request types"""
@ -58,24 +55,24 @@ class SourceTypeManager:
"""Get list of tuples of all registered names""" """Get list of tuples of all registered names"""
return [(x.slug, x.name) for x in self.__sources] return [(x.slug, x.name) for x in self.__sources]
def find_type(self, source: "OAuthSource") -> SourceType: def find_type(self, type_name: str) -> SourceType:
"""Find type based on source""" """Find type based on source"""
found_type = None found_type = None
for src_type in self.__sources: for src_type in self.__sources:
if src_type.slug == source.provider_type: if src_type.slug == type_name:
return src_type return src_type
if not found_type: if not found_type:
found_type = SourceType() found_type = SourceType()
LOGGER.warning( LOGGER.warning(
"no matching type found, using default", "no matching type found, using default",
wanted=source.provider_type, wanted=type_name,
have=[x.name for x in self.__sources], have=[x.slug for x in self.__sources],
) )
return found_type return found_type
def find(self, source: "OAuthSource", kind: RequestKind) -> Callable: def find(self, type_name: str, kind: RequestKind) -> Callable:
"""Find fitting Source Type""" """Find fitting Source Type"""
found_type = self.find_type(source) found_type = self.find_type(type_name)
if kind == RequestKind.CALLBACK: if kind == RequestKind.CALLBACK:
return found_type.callback_view return found_type.callback_view
if kind == RequestKind.REDIRECT: if kind == RequestKind.REDIRECT:

View File

@ -0,0 +1,134 @@
"""Plex OAuth Views"""
from typing import Any, Optional
from urllib.parse import urlencode
from django.http.response import Http404
from requests import post
from requests.api import get
from requests.exceptions import RequestException
from structlog.stdlib import get_logger
from authentik import __version__
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
from authentik.sources.oauth.types.manager import MANAGER, SourceType
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
LOGGER = get_logger()
SESSION_ID_KEY = "PLEX_ID"
SESSION_CODE_KEY = "PLEX_CODE"
DEFAULT_PAYLOAD = {
"X-Plex-Product": "authentik",
"X-Plex-Version": __version__,
"X-Plex-Device-Vendor": "BeryJu.org",
}
class PlexRedirect(OAuthRedirect):
"""Plex Auth redirect, get a pin then redirect to a URL to claim it"""
headers = {}
def get_pin(self, **data) -> dict:
"""Get plex pin that the user will claim
https://forums.plex.tv/t/authenticating-with-plex/609370"""
return post(
"https://plex.tv/api/v2/pins.json?strong=true",
data=data,
headers=self.headers,
).json()
def get_redirect_url(self, **kwargs) -> str:
slug = kwargs.get("source_slug", "")
self.headers = {"Origin": self.request.build_absolute_uri("/")}
try:
source: OAuthSource = OAuthSource.objects.get(slug=slug)
except OAuthSource.DoesNotExist:
raise Http404(f"Unknown OAuth source '{slug}'.")
else:
payload = DEFAULT_PAYLOAD.copy()
payload["X-Plex-Client-Identifier"] = source.consumer_key
# Get a pin first
pin = self.get_pin(**payload)
LOGGER.debug("Got pin", **pin)
self.request.session[SESSION_ID_KEY] = pin["id"]
self.request.session[SESSION_CODE_KEY] = pin["code"]
qs = {
"clientID": source.consumer_key,
"code": pin["code"],
"forwardUrl": self.request.build_absolute_uri(
self.get_callback_url(source)
),
}
return f"https://app.plex.tv/auth#!?{urlencode(qs)}"
class PlexOAuthClient(OAuth2Client):
"""Retrive the plex token after authentication, then ask the plex API about user info"""
def check_application_state(self) -> bool:
return SESSION_ID_KEY in self.request.session
def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]:
payload = dict(DEFAULT_PAYLOAD)
payload["X-Plex-Client-Identifier"] = self.source.consumer_key
payload["Accept"] = "application/json"
response = get(
f"https://plex.tv/api/v2/pins/{self.request.session[SESSION_ID_KEY]}",
headers=payload,
)
response.raise_for_status()
token = response.json()["authToken"]
return {"plex_token": token}
def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
"Fetch user profile information."
qs = {"X-Plex-Token": token["plex_token"]}
try:
response = self.do_request(
"get", f"https://plex.tv/users/account.json?{urlencode(qs)}"
)
response.raise_for_status()
except RequestException as exc:
LOGGER.warning("Unable to fetch user profile", exc=exc)
return None
else:
return response.json().get("user", {})
class PlexOAuth2Callback(OAuthCallback):
"""Plex OAuth2 Callback"""
client_class = PlexOAuthClient
def get_user_id(
self, source: UserOAuthSourceConnection, info: dict[str, Any]
) -> Optional[str]:
return info.get("uuid")
def get_user_enroll_context(
self,
source: OAuthSource,
access: UserOAuthSourceConnection,
info: dict[str, Any],
) -> dict[str, Any]:
return {
"username": info.get("username"),
"email": info.get("email"),
"name": info.get("title"),
}
@MANAGER.type()
class PlexType(SourceType):
"""Plex Type definition"""
redirect_view = PlexRedirect
callback_view = PlexOAuth2Callback
name = "Plex"
slug = "plex"
authorization_url = ""
access_token_url = "" # nosec
profile_url = ""

View File

@ -209,9 +209,9 @@ class OAuthCallback(OAuthClientMixin, View):
) )
return redirect( return redirect(
reverse( reverse(
"authentik_sources_oauth:oauth-client-user", "authentik_core:if-admin",
kwargs={"source_slug": self.source.slug},
) )
+ f"#/user;page-{self.source.slug}"
) )
def handle_enroll( def handle_enroll(

View File

@ -21,6 +21,6 @@ class DispatcherView(View):
if not slug: if not slug:
raise Http404 raise Http404
source = get_object_or_404(OAuthSource, slug=slug) source = get_object_or_404(OAuthSource, slug=slug)
view = MANAGER.find(source, kind=RequestKind(self.kind)) view = MANAGER.find(source.provider_type, kind=RequestKind(self.kind))
LOGGER.debug("dispatching OAuth2 request to", view=view, kind=self.kind) LOGGER.debug("dispatching OAuth2 request to", view=view, kind=self.kind)
return view.as_view()(*args, **kwargs) return view.as_view()(*args, **kwargs)

View File

@ -52,7 +52,6 @@ class EmailStageViewSet(ModelViewSet):
queryset = EmailStage.objects.all() queryset = EmailStage.objects.all()
serializer_class = EmailStageSerializer serializer_class = EmailStageSerializer
# TODO: Validate connection settings when use_global_settings is unchecked
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def templates(self, request: Request) -> Response: def templates(self, request: Request) -> Response:
@ -64,6 +63,7 @@ class EmailStageViewSet(ModelViewSet):
"name": value, "name": value,
"description": label, "description": label,
"component": "", "component": "",
"model_name": "",
} }
) )
return Response(TypeCreateSerializer(choices, many=True).data) return Response(TypeCreateSerializer(choices, many=True).data)

View File

@ -1,7 +1,9 @@
"""Invitation Stage API Views""" """Invitation Stage API Views"""
from rest_framework.fields import JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import is_dict
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.invitation.models import Invitation, InvitationStage from authentik.stages.invitation.models import Invitation, InvitationStage
@ -27,6 +29,8 @@ class InvitationStageViewSet(ModelViewSet):
class InvitationSerializer(ModelSerializer): class InvitationSerializer(ModelSerializer):
"""Invitation Serializer""" """Invitation Serializer"""
fixed_data = JSONField(validators=[is_dict], required=False)
class Meta: class Meta:
model = Invitation model = Invitation

View File

@ -142,7 +142,9 @@ class TestInvitationsAPI(APITestCase):
def test_invite_create(self): def test_invite_create(self):
"""Test Invitations creation endpoint""" """Test Invitations creation endpoint"""
response = self.client.post( response = self.client.post(
reverse("authentik_api:invitation-list"), {"identifier": "test-token"} reverse("authentik_api:invitation-list"),
{"identifier": "test-token", "fixed_data": {}},
format="json",
) )
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(Invitation.objects.first().created_by, self.user) self.assertEqual(Invitation.objects.first().created_by, self.user)

View File

@ -4,6 +4,7 @@ version: '3.2'
services: services:
postgresql: postgresql:
image: postgres:12 image: postgres:12
restart: unless-stopped
volumes: volumes:
- database:/var/lib/postgresql/data - database:/var/lib/postgresql/data
networks: networks:
@ -19,7 +20,8 @@ services:
networks: networks:
- internal - internal
server: server:
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.1-rc1} image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.3}
restart: unless-stopped
command: server command: server
environment: environment:
AUTHENTIK_REDIS__HOST: redis AUTHENTIK_REDIS__HOST: redis
@ -33,8 +35,6 @@ services:
- ./media:/media - ./media:/media
- ./custom-templates:/templates - ./custom-templates:/templates
- geoip:/geoip - geoip:/geoip
ports:
- 8000
networks: networks:
- internal - internal
labels: labels:
@ -48,7 +48,8 @@ services:
env_file: env_file:
- .env - .env
worker: worker:
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.1-rc1} image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.3}
restart: unless-stopped
command: worker command: worker
networks: networks:
- internal - internal
@ -67,7 +68,8 @@ services:
env_file: env_file:
- .env - .env
static: static:
image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.4.1-rc1} image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.4.3}
restart: unless-stopped
networks: networks:
- internal - internal
labels: labels:
@ -76,13 +78,14 @@ services:
traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/if`, `/media`, `/robots.txt`, `/favicon.ico`) traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/if`, `/media`, `/robots.txt`, `/favicon.ico`)
traefik.http.routers.static-router.tls: 'true' traefik.http.routers.static-router.tls: 'true'
traefik.http.routers.static-router.service: static-service traefik.http.routers.static-router.service: static-service
traefik.http.services.static-service.loadbalancer.healthcheck.path: /-/health/ready/ traefik.http.services.static-service.loadbalancer.healthcheck.path: /
traefik.http.services.static-service.loadbalancer.healthcheck.interval: 30s traefik.http.services.static-service.loadbalancer.healthcheck.interval: 30s
traefik.http.services.static-service.loadbalancer.server.port: '80' traefik.http.services.static-service.loadbalancer.server.port: '80'
volumes: volumes:
- ./media:/usr/share/nginx/html/media - ./media:/usr/share/nginx/html/media
traefik: traefik:
image: traefik:2.3 image: traefik:2.3
restart: unless-stopped
command: command:
- "--log.format=json" - "--log.format=json"
- "--api.insecure=true" - "--api.insecure=true"

View File

@ -4,7 +4,7 @@ name: authentik
home: https://goauthentik.io home: https://goauthentik.io
sources: sources:
- https://github.com/BeryJu/authentik - https://github.com/BeryJu/authentik
version: "2021.4.1-rc1" version: "2021.4.3"
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
dependencies: dependencies:
- name: postgresql - name: postgresql

View File

@ -4,12 +4,12 @@
|-----------------------------------|-------------------------|-------------| |-----------------------------------|-------------------------|-------------|
| image.name | beryju/authentik | Image used to run the authentik server and worker | | image.name | beryju/authentik | Image used to run the authentik server and worker |
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) | | image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
| image.tag | 2021.4.1-rc1 | Image tag | | image.tag | 2021.4.3 | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments | | image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment | | serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment | | workerReplicas | 1 | Replicas for the Worker deployment |
| kubernetesIntegration | true | Enable/disable the Kubernetes integration for authentik. This will create a service account for authentik to create and update outposts in authentik | | kubernetesIntegration | true | Enable/disable the Kubernetes integration for authentik. This will create a service account for authentik to create and update outposts in authentik |
| config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` for example. | | config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` or `openssl rand -base64 36` for example. |
| config.errorReporting.enabled | false | Enable/disable error reporting | | config.errorReporting.enabled | false | Enable/disable error reporting |
| config.errorReporting.environment | customer | Environment sent with the error reporting | | config.errorReporting.environment | customer | Environment sent with the error reporting |
| config.errorReporting.sendPii | false | Whether to send Personally-identifiable data with the error reporting | | config.errorReporting.sendPii | false | Whether to send Personally-identifiable data with the error reporting |
@ -22,6 +22,11 @@
| config.email.use_ssl | false | Enable SSL | | config.email.use_ssl | false | Enable SSL |
| config.email.timeout | 10 | SMTP Timeout | | config.email.timeout | 10 | SMTP Timeout |
| config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain | | config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain |
| pvc.mode | ReadWriteMany | Mode that the PVCs are created in (uploads and GeoIP, if enabled) |
| pvc.uploadsSize | 5Gi | Size for the uploads PVC |
| pvc.uploadsStorageClass | null | Storage class for the uploads PVC (default: use default storage class) |
| pvc.geoIpSize | 1Gi | Size for the GeoIP PVC |
| pvc.geoIpStorageClass | null | Storage class for the GeoIP PVC (default: use default storage class) |
| geoip.enabled | false | Optionally enable GeoIP | | geoip.enabled | false | Optionally enable GeoIP |
| geoip.accountId | | GeoIP MaxMind Account ID | | geoip.accountId | | GeoIP MaxMind Account ID |
| geoip.licenseKey | | GeoIP MaxMind License key | | geoip.licenseKey | | GeoIP MaxMind License key |

View File

@ -10,8 +10,9 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
spec: spec:
accessModes: accessModes:
- ReadWriteMany - {{ .Values.pvc.mode }}
storageClassName: {{ .Values.pvc.geoIpStorageClass }}
resources: resources:
requests: requests:
storage: 1Gi storage: {{ .Values.pvc.geoIpSize }}
{{- end }} {{- end }}

View File

@ -9,7 +9,8 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
spec: spec:
accessModes: accessModes:
- ReadWriteMany - {{ .Values.pvc.mode }}
storageClassName: {{ .Values.pvc.uploadsStorageClass }}
resources: resources:
requests: requests:
storage: 5Gi storage: {{ .Values.pvc.uploadsSize }}

View File

@ -43,29 +43,6 @@ spec:
values: values:
- web - web
topologyKey: "kubernetes.io/hostname" topologyKey: "kubernetes.io/hostname"
initContainers:
- name: authentik-database-migrations
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
args: [migrate]
envFrom:
- configMapRef:
name: {{ include "authentik.fullname" . }}-config
prefix: AUTHENTIK_
- secretRef:
name: {{ include "authentik.fullname" . }}-secret-key
prefix: AUTHENTIK_
env:
- name: AUTHENTIK_REDIS__PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-redis"
key: redis-password
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-postgresql"
key: postgresql-password
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}" image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"

View File

@ -5,7 +5,7 @@ image:
name: beryju/authentik name: beryju/authentik
name_static: beryju/authentik-static name_static: beryju/authentik-static
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
tag: 2021.4.1-rc1 tag: 2021.4.3
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
serverReplicas: 1 serverReplicas: 1
@ -15,7 +15,14 @@ workerReplicas: 1
kubernetesIntegration: true kubernetesIntegration: true
monitoring: monitoring:
enabled: true enabled: false
pvc:
mode: ReadWriteMany
uploadsSize: 5Gi
uploadsStorageClass: null
geoIpSize: 1Gi
geoIpStorageClass: null
config: config:
# Optionally specify fixed secret_key, otherwise generated automatically # Optionally specify fixed secret_key, otherwise generated automatically

View File

@ -2,13 +2,13 @@
python -m lifecycle.wait_for_db python -m lifecycle.wait_for_db
printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr
if [[ "$1" == "server" ]]; then if [[ "$1" == "server" ]]; then
python -m lifecycle.migrate
gunicorn -c /lifecycle/gunicorn.conf.py authentik.root.asgi:application gunicorn -c /lifecycle/gunicorn.conf.py authentik.root.asgi:application
elif [[ "$1" == "worker" ]]; then elif [[ "$1" == "worker" ]]; then
celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events
elif [[ "$1" == "migrate" ]]; then elif [[ "$1" == "migrate" ]]; then
# Run system migrations first, run normal migrations after printf "DEPERECATED: database migrations are now executed automatically on startup."
python -m lifecycle.migrate python -m lifecycle.migrate
python -m manage migrate
elif [[ "$1" == "backup" ]]; then elif [[ "$1" == "backup" ]]; then
python -m manage dbbackup --clean python -m manage dbbackup --clean
elif [[ "$1" == "restore" ]]; then elif [[ "$1" == "restore" ]]; then

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
"""System Migration handler""" """System Migration handler"""
import os
from importlib.util import module_from_spec, spec_from_file_location from importlib.util import module_from_spec, spec_from_file_location
from inspect import getmembers, isclass from inspect import getmembers, isclass
from pathlib import Path from pathlib import Path
@ -11,6 +12,7 @@ from structlog.stdlib import get_logger
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
LOGGER = get_logger() LOGGER = get_logger()
ADV_LOCK_UID = 1000
class BaseMigration: class BaseMigration:
@ -40,18 +42,36 @@ if __name__ == "__main__":
host=CONFIG.y("postgresql.host"), host=CONFIG.y("postgresql.host"),
) )
curr = conn.cursor() curr = conn.cursor()
# lock an advisory lock to prevent multiple instances from migrating at once
LOGGER.info("waiting to acquire database lock")
curr.execute("SELECT pg_advisory_lock(%s)", (ADV_LOCK_UID,))
try:
for migration in (
Path(__file__).parent.absolute().glob("system_migrations/*.py")
):
spec = spec_from_file_location("lifecycle.system_migrations", migration)
mod = module_from_spec(spec)
# pyright: reportGeneralTypeIssues=false
spec.loader.exec_module(mod)
for migration in Path(__file__).parent.absolute().glob("system_migrations/*.py"): for name, sub in getmembers(mod, isclass):
spec = spec_from_file_location("lifecycle.system_migrations", migration) if name != "Migration":
mod = module_from_spec(spec) continue
# pyright: reportGeneralTypeIssues=false migration = sub(curr, conn)
spec.loader.exec_module(mod) if migration.needs_migration():
LOGGER.info("Migration needs to be applied", migration=sub)
for name, sub in getmembers(mod, isclass): migration.run()
if name != "Migration": LOGGER.info("Migration finished applying", migration=sub)
continue LOGGER.info("applying django migrations")
migration = sub(curr, conn) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
if migration.needs_migration(): try:
LOGGER.info("Migration needs to be applied", migration=sub) from django.core.management import execute_from_command_line
migration.run() except ImportError as exc:
LOGGER.info("Migration finished applying", migration=sub) raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(["", "migrate"])
finally:
curr.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))

View File

@ -18,15 +18,12 @@ stages:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.15' version: '1.16.3'
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: | script: |
sudo apt install gnupg ca-certificates sudo wget -O /usr/local/bin/swagger https://github.com/go-swagger/go-swagger/releases/latest/download/swagger_linux_amd64
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 379CE192D401AB61 sudo chmod +x /usr/local/bin/swagger
echo "deb https://dl.bintray.com/go-swagger/goswagger-debian ubuntu main" | sudo tee /etc/apt/sources.list.d/goswagger.list
sudo apt update
sudo apt install swagger
mkdir -p $(go env GOPATH) mkdir -p $(go env GOPATH)
swagger generate client -f ../swagger.yaml -A authentik -t pkg/ swagger generate client -f ../swagger.yaml -A authentik -t pkg/
workingDirectory: 'outpost/' workingDirectory: 'outpost/'
@ -43,11 +40,7 @@ stages:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.15' version: '1.16.3'
- task: Go@0
inputs:
command: 'get'
arguments: '-u golang.org/x/lint/golint'
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'
@ -56,7 +49,7 @@ stages:
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
script: | script: |
$(go list -f {{.Target}} golang.org/x/lint/golint) ./... docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.39.0 golangci-lint run -v --timeout 200s
workingDirectory: 'outpost/' workingDirectory: 'outpost/'
- stage: proxy_build_go - stage: proxy_build_go
jobs: jobs:
@ -66,7 +59,7 @@ stages:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.15' version: '1.16.3'
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'
@ -85,7 +78,7 @@ stages:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.15' version: '1.16.3'
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'

View File

@ -52,13 +52,14 @@ func main() {
ac.Server = proxy.NewServer(ac) ac.Server = proxy.NewServer(ac)
ac.Start() err = ac.Start()
if err != nil {
log.WithError(err).Panic("Failed to run server")
}
for { for {
select { <-interrupt
case <-interrupt: ac.Shutdown()
ac.Shutdown() os.Exit(0)
os.Exit(0)
}
} }
} }

View File

@ -6,13 +6,14 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getsentry/sentry-go v0.10.0 github.com/getsentry/sentry-go v0.10.0
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 github.com/go-openapi/errors v0.20.0
github.com/go-openapi/runtime v0.19.27 github.com/go-openapi/runtime v0.19.28
github.com/go-openapi/strfmt v0.20.1 github.com/go-openapi/strfmt v0.20.1
github.com/go-openapi/swag v0.19.15 github.com/go-openapi/swag v0.19.15
github.com/go-openapi/validate v0.20.2 github.com/go-openapi/validate v0.20.2
github.com/go-redis/redis/v7 v7.4.0 // indirect github.com/go-redis/redis/v7 v7.4.0 // indirect
github.com/golang/protobuf v1.4.3 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/justinas/alice v1.2.0 github.com/justinas/alice v1.2.0
@ -20,9 +21,9 @@ require (
github.com/magiconair/properties v1.8.5 // indirect github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
github.com/pelletier/go-toml v1.8.1 // indirect github.com/pelletier/go-toml v1.9.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/recws-org/recws v1.3.1 github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.6.0 // indirect
@ -30,9 +31,10 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect github.com/spf13/viper v1.7.1 // indirect
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5 // indirect golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d // indirect
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect

View File

@ -154,8 +154,9 @@ github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9sn
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro=
github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
github.com/go-openapi/analysis v0.20.1 h1:zdVbw8yoD4SWZeq+cWdGgquaB0W4VrsJvDJHJND/Ktc=
github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
@ -195,8 +196,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/runtime v0.19.27 h1:4zrQCJoP7rqNCUaApDv1MdPkaa5TuPfO05Lq5WLhX9I= github.com/go-openapi/runtime v0.19.28 h1:9lYu6axek8LJrVkMVViVirRcpoaCxXX7+sSvmizGVnA=
github.com/go-openapi/runtime v0.19.27/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
@ -300,8 +301,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
@ -316,8 +318,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -503,8 +506,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@ -518,8 +521,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e h1:BLqxdwZ6j771IpSCRx7s/GJjXHUE00Hmu7/YegCGdzA= github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -662,8 +665,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -738,15 +741,16 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5 h1:zuP3axpB9rV3xH0EA7n3/gCrNPZm2SRl0l4mVH2BRj4= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -806,9 +810,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -816,8 +820,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -957,8 +962,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net/url" "net/url"
"os"
"time" "time"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
@ -39,12 +40,12 @@ type APIController struct {
} }
// NewAPIController initialise new API Controller instance from URL and API token // NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(pbURL url.URL, token string) *APIController { func NewAPIController(akURL url.URL, token string) *APIController {
transport := httptransport.New(pbURL.Host, client.DefaultBasePath, []string{pbURL.Scheme}) transport := httptransport.New(akURL.Host, client.DefaultBasePath, []string{akURL.Scheme})
transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION)) transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION))
// create the transport // create the transport
auth := httptransport.BasicAuth("", token) auth := httptransport.BearerToken(token)
// create the API client, with the transport // create the API client, with the transport
apiClient := client.New(transport, strfmt.Default) apiClient := client.New(transport, strfmt.Default)
@ -53,10 +54,11 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
// Because we don't know the outpost UUID, we simply do a list and pick the first // Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost // The service account this token belongs to should only have access to a single outpost
outposts, err := apiClient.Outposts.OutpostsOutpostsList(outposts.NewOutpostsOutpostsListParams(), auth) outposts, err := apiClient.Outposts.OutpostsInstancesList(outposts.NewOutpostsInstancesListParams(), auth)
if err != nil { if err != nil {
log.WithError(err).Panic("Failed to fetch configuration") log.WithError(err).Error("Failed to fetch configuration")
os.Exit(1)
} }
outpost := outposts.Payload.Results[0] outpost := outposts.Payload.Results[0]
doGlobalSetup(outpost.Config.(map[string]interface{})) doGlobalSetup(outpost.Config.(map[string]interface{}))
@ -73,7 +75,7 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
lastBundleHash: "", lastBundleHash: "",
} }
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset) ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
ac.initWS(pbURL, outpost.Pk) ac.initWS(akURL, outpost.Pk)
return ac return ac
} }
@ -96,7 +98,10 @@ func (a *APIController) Start() error {
a.startWSHealth() a.startWSHealth()
}() }()
go func() { go func() {
a.Server.Start() err := a.Server.Start()
if err != nil {
panic(err)
}
}() }()
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package ak
import ( import (
"crypto/tls" "crypto/tls"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -20,7 +19,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
pathTemplate := "%s://%s/ws/outpost/%s/" pathTemplate := "%s://%s/ws/outpost/%s/"
scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws") scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws")
authHeader := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("Basic :%s", ac.token))) authHeader := fmt.Sprintf("Bearer %s", ac.token)
header := http.Header{ header := http.Header{
"Authorization": []string{authHeader}, "Authorization": []string{authHeader},
@ -65,7 +64,6 @@ func (ac *APIController) Shutdown() {
ac.logger.Println("write close:", err) ac.logger.Println("write close:", err)
return return
} }
return
} }
func (ac *APIController) startWSHandler() { func (ac *APIController) startWSHandler() {
@ -93,7 +91,8 @@ func (ac *APIController) startWSHandler() {
} }
func (ac *APIController) startWSHealth() { func (ac *APIController) startWSHealth() {
for ; true; <-time.Tick(time.Second * 10) { ticker := time.NewTicker(time.Second * 10)
for ; true; <-ticker.C {
if !ac.wsConn.IsConnected() { if !ac.wsConn.IsConnected() {
continue continue
} }

View File

@ -38,7 +38,11 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
return nil return nil
} }
providerOpts := &options.Options{} providerOpts := &options.Options{}
copier.Copy(&providerOpts, getCommonOptions()) err = copier.Copy(&providerOpts, getCommonOptions())
if err != nil {
log.WithError(err).Warning("Failed to copy options, skipping provider")
return nil
}
providerOpts.ClientID = provider.ClientID providerOpts.ClientID = provider.ClientID
providerOpts.ClientSecret = provider.ClientSecret providerOpts.ClientSecret = provider.ClientSecret
@ -62,7 +66,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
ID: "default", ID: "default",
URI: *provider.InternalHost, URI: *provider.InternalHost,
Path: "/", Path: "/",
InsecureSkipTLSVerify: *&provider.InternalHostSslValidation, InsecureSkipTLSVerify: provider.InternalHostSslValidation,
}, },
} }
@ -85,7 +89,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
return providerOpts return providerOpts
} }
x509cert, err := tls.X509KeyPair([]byte(*&cert.Payload.Data), []byte(key.Payload.Data)) x509cert, err := tls.X509KeyPair([]byte(cert.Payload.Data), []byte(key.Payload.Data))
if err != nil { if err != nil {
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate") pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate")
return providerOpts return providerOpts
@ -135,7 +139,7 @@ func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
os.Exit(1) os.Exit(1)
} }
if *&provider.BasicAuthEnabled { if provider.BasicAuthEnabled {
oauthproxy.SetBasicAuth = true oauthproxy.SetBasicAuth = true
oauthproxy.BasicAuthUserAttribute = provider.BasicAuthUserAttribute oauthproxy.BasicAuthUserAttribute = provider.BasicAuthUserAttribute
oauthproxy.BasicAuthPasswordAttribute = provider.BasicAuthPasswordAttribute oauthproxy.BasicAuthPasswordAttribute = provider.BasicAuthPasswordAttribute

View File

@ -1,3 +1,3 @@
package pkg package pkg
const VERSION = "2021.4.1-rc1" const VERSION = "2021.4.3"

View File

@ -13,12 +13,12 @@ consumes:
produces: produces:
- application/json - application/json
securityDefinitions: securityDefinitions:
token: Bearer:
type: apiKey type: apiKey
name: Authorization name: Authorization
in: header in: header
security: security:
- token: [] - Bearer: []
paths: paths:
/admin/apps/: /admin/apps/:
get: get:
@ -131,27 +131,7 @@ paths:
get: get:
operationId: admin_version_list operationId: admin_version_list
description: Get running and latest version. description: Get running and latest version.
parameters: parameters: []
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: Page Index
required: false
type: integer
- name: page_size
in: query
description: Page Size
required: false
type: integer
responses: responses:
'200': '200':
description: '' description: ''
@ -1172,6 +1152,9 @@ paths:
description: Page Size description: Page Size
required: false required: false
type: integer type: integer
- name: superuser_full_list
in: query
type: boolean
responses: responses:
'200': '200':
description: '' description: ''
@ -2257,15 +2240,15 @@ paths:
description: '' description: ''
schema: schema:
$ref: '#/definitions/Link' $ref: '#/definitions/Link'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404': '404':
description: Object does not exist or caller has insufficient permissions description: Object does not exist or caller has insufficient permissions
to access it. to access it.
schema: schema:
$ref: '#/definitions/APIException' $ref: '#/definitions/APIException'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags: tags:
- core - core
parameters: parameters:
@ -3684,15 +3667,15 @@ paths:
description: '' description: ''
schema: schema:
$ref: '#/definitions/Challenge' $ref: '#/definitions/Challenge'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404': '404':
description: Object does not exist or caller has insufficient permissions description: Object does not exist or caller has insufficient permissions
to access it. to access it.
schema: schema:
$ref: '#/definitions/APIException' $ref: '#/definitions/APIException'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags: tags:
- flows - flows
post: post:
@ -4386,6 +4369,281 @@ paths:
description: A unique integer value identifying this OAuth2 Token. description: A unique integer value identifying this OAuth2 Token.
required: true required: true
type: integer type: integer
/outposts/instances/:
get:
operationId: outposts_instances_list
description: Outpost Viewset
parameters:
- name: providers__isnull
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: Page Index
required: false
type: integer
- name: page_size
in: query
description: Page Size
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- results
- pagination
type: object
properties:
pagination:
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
results:
type: array
items:
$ref: '#/definitions/Outpost'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- outposts
post:
operationId: outposts_instances_create
description: Outpost Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Outpost'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/Outpost'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- outposts
parameters: []
/outposts/instances/default_settings/:
get:
operationId: outposts_instances_default_settings
description: Global default outpost config
parameters:
- name: providers__isnull
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: Page Index
required: false
type: integer
- name: page_size
in: query
description: Page Size
required: false
type: integer
responses:
'200':
description: ''
schema:
$ref: '#/definitions/OutpostDefaultConfig'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- outposts
parameters: []
/outposts/instances/{uuid}/:
get:
operationId: outposts_instances_read
description: Outpost Viewset
parameters: []
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Outpost'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404':
description: Object does not exist or caller has insufficient permissions
to access it.
schema:
$ref: '#/definitions/APIException'
tags:
- outposts
put:
operationId: outposts_instances_update
description: Outpost Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Outpost'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Outpost'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404':
description: Object does not exist or caller has insufficient permissions
to access it.
schema:
$ref: '#/definitions/APIException'
tags:
- outposts
patch:
operationId: outposts_instances_partial_update
description: Outpost Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Outpost'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Outpost'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404':
description: Object does not exist or caller has insufficient permissions
to access it.
schema:
$ref: '#/definitions/APIException'
tags:
- outposts
delete:
operationId: outposts_instances_delete
description: Outpost Viewset
parameters: []
responses:
'204':
description: ''
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404':
description: Object does not exist or caller has insufficient permissions
to access it.
schema:
$ref: '#/definitions/APIException'
tags:
- outposts
parameters:
- name: uuid
in: path
description: A UUID string identifying this outpost.
required: true
type: string
format: uuid
/outposts/instances/{uuid}/health/:
get:
operationId: outposts_instances_health
description: Get outposts current health
parameters: []
responses:
'200':
description: ''
schema:
type: array
items:
$ref: '#/definitions/OutpostHealth'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
'404':
description: Object does not exist or caller has insufficient permissions
to access it.
schema:
$ref: '#/definitions/APIException'
tags:
- outposts
parameters:
- name: uuid
in: path
description: A UUID string identifying this outpost.
required: true
type: string
format: uuid
/outposts/outposts/: /outposts/outposts/:
get: get:
operationId: outposts_outposts_list operationId: outposts_outposts_list
@ -7691,6 +7949,9 @@ paths:
required: true required: true
schema: schema:
$ref: '#/definitions/PolicyTest' $ref: '#/definitions/PolicyTest'
- name: format_result
in: query
type: boolean
responses: responses:
'200': '200':
description: '' description: ''
@ -10285,7 +10546,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: '#/definitions/UserSetting' $ref: '#/definitions/StageUserSetting'
'403': '403':
description: Authentication credentials were invalid, absent or insufficient. description: Authentication credentials were invalid, absent or insufficient.
schema: schema:
@ -14791,6 +15052,7 @@ definitions:
- name - name
- description - description
- component - component
- model_name
type: object type: object
properties: properties:
name: name:
@ -14805,6 +15067,10 @@ definitions:
title: Component title: Component
type: string type: string
minLength: 1 minLength: 1
model_name:
title: Model name
type: string
minLength: 1
EventTopPerUser: EventTopPerUser:
required: required:
- application - application
@ -16983,9 +17249,6 @@ definitions:
- name - name
- slug - slug
- provider_type - provider_type
- authorization_url
- access_token_url
- profile_url
- consumer_key - consumer_key
- consumer_secret - consumer_secret
type: object type: object
@ -17052,24 +17315,29 @@ definitions:
for OAuth 1. for OAuth 1.
type: string type: string
maxLength: 255 maxLength: 255
minLength: 1
x-nullable: true
authorization_url: authorization_url:
title: Authorization URL title: Authorization URL
description: URL the user is redirect to to conest the flow. description: URL the user is redirect to to conest the flow.
type: string type: string
maxLength: 255 maxLength: 255
minLength: 1 minLength: 1
x-nullable: true
access_token_url: access_token_url:
title: Access Token URL title: Access Token URL
description: URL used by authentik to retrive tokens. description: URL used by authentik to retrive tokens.
type: string type: string
maxLength: 255 maxLength: 255
minLength: 1 minLength: 1
x-nullable: true
profile_url: profile_url:
title: Profile URL title: Profile URL
description: URL used by authentik to get user information. description: URL used by authentik to get user information.
type: string type: string
maxLength: 255 maxLength: 255
minLength: 1 minLength: 1
x-nullable: true
consumer_key: consumer_key:
title: Consumer key title: Consumer key
type: string type: string
@ -17242,6 +17510,28 @@ definitions:
\ log out manually. (Format: hours=1;minutes=2;seconds=3)." \ log out manually. (Format: hours=1;minutes=2;seconds=3)."
type: string type: string
minLength: 1 minLength: 1
StageUserSetting:
required:
- object_uid
- component
- title
type: object
properties:
object_uid:
title: Object uid
type: string
minLength: 1
component:
title: Component
type: string
minLength: 1
title:
title: Title
type: string
minLength: 1
configure_flow:
title: Configure flow
type: boolean
AuthenticatorStaticStage: AuthenticatorStaticStage:
required: required:
- name - name
@ -17725,7 +18015,6 @@ definitions:
x-nullable: true x-nullable: true
fixed_data: fixed_data:
title: Fixed data title: Fixed data
description: Optional fixed data to enforce on user enrollment.
type: object type: object
created_by: created_by:
required: required:

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title/><path d="M11.643 0H4.68l7.679 12L4.68 24h6.963l7.677-12-7.677-12"/></svg>

After

Width:  |  Height:  |  Size: 175 B

View File

@ -81,7 +81,7 @@ http {
location /static/ { location /static/ {
expires 31d; expires 31d;
add_header Cache-Control "public, no-transform"; add_header Cache-Control "public, no-transform";
add_header X-authentik-version "2021.4.1-rc1"; add_header X-authentik-version "2021.4.3";
add_header Vary X-authentik-version; add_header Vary X-authentik-version;
} }

277
web/package-lock.json generated
View File

@ -1336,28 +1336,86 @@
} }
}, },
"@lingui/babel-plugin-extract-messages": { "@lingui/babel-plugin-extract-messages": {
"version": "3.8.6", "version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.8.6.tgz", "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.8.9.tgz",
"integrity": "sha512-SJoeg6G+vh3AYWDBuwsXitV7YTp9JxUwjPll+TKIsuMITvaOq8+lm1gt9fIhu1FiwmoddmW9wNd9+R231I4TiQ==", "integrity": "sha512-zPpSl89nvUrLyGHfVosZHCP9fylfCfkEMc29wGdjE6f0U+frJ59NRLilWMy7xaE8uz97cD5vkhYaaF1wnavhxA==",
"requires": { "requires": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "^3.8.6", "@lingui/conf": "^3.8.9",
"mkdirp": "^1.0.4" "mkdirp": "^1.0.4"
},
"dependencies": {
"@lingui/conf": {
"version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
"integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
"requires": {
"@babel/runtime": "^7.11.2",
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
"chalk": "^4.1.0",
"cosmiconfig": "^7.0.0",
"jest-validate": "^26.5.2",
"lodash.get": "^4.4.2"
}
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
}
} }
}, },
"@lingui/cli": { "@lingui/cli": {
"version": "3.8.6", "version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.8.6.tgz", "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.8.9.tgz",
"integrity": "sha512-D3r6U6W/CGch8rEd/IFgImhoIlKePtyWLEwmybKnNfXjbtWBVtnVWroHl5IHYkXw9oGbOwxJDERMduR3D6IItw==", "integrity": "sha512-UccLtfwrTjXrZcTxpqA4ggYhuUMbXZtzbUVks8nDVt3emVqU56C3VMvVD8WKXLL8Qmq9cEDXPwIZy7IKRL4mEQ==",
"requires": { "requires": {
"@babel/generator": "^7.11.6", "@babel/generator": "^7.11.6",
"@babel/parser": "^7.11.5", "@babel/parser": "^7.11.5",
"@babel/plugin-syntax-jsx": "^7.10.4", "@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@babel/types": "^7.11.5", "@babel/types": "^7.11.5",
"@lingui/babel-plugin-extract-messages": "^3.8.6", "@lingui/babel-plugin-extract-messages": "^3.8.9",
"@lingui/conf": "^3.8.6", "@lingui/conf": "^3.8.9",
"babel-plugin-macros": "^3.0.1", "babel-plugin-macros": "^3.0.1",
"bcp-47": "^1.0.7", "bcp-47": "^1.0.7",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@ -1384,6 +1442,19 @@
"ramda": "^0.27.1" "ramda": "^0.27.1"
}, },
"dependencies": { "dependencies": {
"@lingui/conf": {
"version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
"integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
"requires": {
"@babel/runtime": "^7.11.2",
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
"chalk": "^4.1.0",
"cosmiconfig": "^7.0.0",
"jest-validate": "^26.5.2",
"lodash.get": "^4.4.2"
}
},
"ansi-styles": { "ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@ -1460,9 +1531,9 @@
} }
}, },
"@lingui/conf": { "@lingui/conf": {
"version": "3.8.6", "version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.6.tgz", "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
"integrity": "sha512-qfTzii2IIt5CBE3vIKYHHY0cH8Nz6gRnIU6DnJhQD76LvdzplAwa7Pt/QwIGJKaV9IvfFZCrNAM34P/hSKl5uQ==", "integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2", "@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
@ -1518,9 +1589,9 @@
} }
}, },
"@lingui/core": { "@lingui/core": {
"version": "3.8.6", "version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.8.6.tgz", "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.8.9.tgz",
"integrity": "sha512-zuyu8tVCsctVK4A9mdlr0v1GJ1o8CpA8FE0M6vSKtz8vnPDsaPdNr9YqME6EW++w4PwhnuMyfrGx8iFIJBzp1A==", "integrity": "sha512-QmEfgukR7w/4/4USZT0LGNt7Yq/RgirFl4088wEta0vgroidxaCRgUXr8RXcdFVjTdtG5dc86JTEj4inZECKvg==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"make-plural": "^6.2.2", "make-plural": "^6.2.2",
@ -1528,12 +1599,12 @@
} }
}, },
"@lingui/macro": { "@lingui/macro": {
"version": "3.8.6", "version": "3.8.9",
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.8.6.tgz", "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.8.9.tgz",
"integrity": "sha512-WABB1ufaIB5pSoSArA0vk808OPfFCkYDzOL1foE11WS69/LEJ+1XwEd2hdFmrYppml9pLCEG+obcd2PbZXHatw==", "integrity": "sha512-9LhlbkJ9wOtOLhlaVRLHCRL55S5wOFyyqEhUM+ujUmCskTmMmXzjnRsw5f11nJTK1JJETMT/VlUB5/p7D7Edkw==",
"requires": { "requires": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"@lingui/conf": "^3.8.6", "@lingui/conf": "^3.8.9",
"ramda": "^0.27.1" "ramda": "^0.27.1"
} }
}, },
@ -1885,9 +1956,9 @@
} }
}, },
"@types/codemirror": { "@types/codemirror": {
"version": "0.0.108", "version": "0.0.109",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
"integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==", "integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
"requires": { "requires": {
"@types/tern": "*" "@types/tern": "*"
} }
@ -2013,12 +2084,12 @@
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz",
"integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==", "integrity": "sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA==",
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.21.0", "@typescript-eslint/experimental-utils": "4.22.0",
"@typescript-eslint/scope-manager": "4.21.0", "@typescript-eslint/scope-manager": "4.22.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",
@ -2028,50 +2099,102 @@
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz",
"integrity": "sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA==", "integrity": "sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg==",
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.21.0", "@typescript-eslint/scope-manager": "4.22.0",
"@typescript-eslint/types": "4.21.0", "@typescript-eslint/types": "4.22.0",
"@typescript-eslint/typescript-estree": "4.21.0", "@typescript-eslint/typescript-estree": "4.22.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.0.0"
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.22.0.tgz",
"integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==", "integrity": "sha512-z/bGdBJJZJN76nvAY9DkJANYgK3nlRstRRi74WHm3jjgf2I8AglrSY+6l7ogxOmn55YJ6oKZCLLy+6PW70z15Q==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.21.0", "@typescript-eslint/scope-manager": "4.22.0",
"@typescript-eslint/types": "4.21.0", "@typescript-eslint/types": "4.22.0",
"@typescript-eslint/typescript-estree": "4.21.0", "@typescript-eslint/typescript-estree": "4.22.0",
"debug": "^4.1.1" "debug": "^4.1.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz",
"integrity": "sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q==",
"requires": {
"@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.22.0"
}
},
"@typescript-eslint/types": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.0.tgz",
"integrity": "sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA=="
},
"@typescript-eslint/typescript-estree": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz",
"integrity": "sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg==",
"requires": {
"@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.22.0",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz",
"integrity": "sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw==",
"requires": {
"@typescript-eslint/types": "4.22.0",
"eslint-visitor-keys": "^2.0.0"
}
},
"globby": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
}
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz",
"integrity": "sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw==", "integrity": "sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q==",
"requires": { "requires": {
"@typescript-eslint/types": "4.21.0", "@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.21.0" "@typescript-eslint/visitor-keys": "4.22.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.0.tgz",
"integrity": "sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w==" "integrity": "sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA=="
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz",
"integrity": "sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w==", "integrity": "sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg==",
"requires": { "requires": {
"@typescript-eslint/types": "4.21.0", "@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.21.0", "@typescript-eslint/visitor-keys": "4.22.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"globby": "^11.0.1", "globby": "^11.0.1",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -2095,11 +2218,11 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.21.0", "version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz",
"integrity": "sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w==", "integrity": "sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw==",
"requires": { "requires": {
"@typescript-eslint/types": "4.21.0", "@typescript-eslint/types": "4.22.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
@ -2524,9 +2647,9 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
}, },
"chart.js": { "chart.js": {
"version": "3.0.2", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.0.2.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.1.1.tgz",
"integrity": "sha512-DR0GmFSlxcFJp/w//ZmbxSduAkH/AqwxoiZxK97KHnWZf6gvsKWS3160WvNMMHYvzW9OXqGWjPjVh1Qu+xDabg==" "integrity": "sha512-ghNJersc9VD9MECwa5bL8gqvCkndW6RSCicdEHL9lIriNtXwKawlSmwo+u6KNXLYT2+f24GdFPBoynKW3ke4MQ=="
}, },
"chartjs-adapter-moment": { "chartjs-adapter-moment": {
"version": "1.0.0", "version": "1.0.0",
@ -2798,9 +2921,9 @@
} }
}, },
"date-fns": { "date-fns": {
"version": "2.20.0", "version": "2.20.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.1.tgz",
"integrity": "sha512-nmA7y6aDH5+fknfJ0G77HQzUSfTPpq4ifq+c9blP9d+X9zs3kNjxC+t3pcbBMGTp262a6PJB3RVjLlxIgoMI+Q==" "integrity": "sha512-8P5M8Kxbnovd0zfvOs7ipkiVJ3/zZQ0F/nrBW4x5E+I0uAZVZ80h6CKd24fSXQ5TLK5hXMtI4yb2O5rEZdUt2A=="
}, },
"debug": { "debug": {
"version": "4.3.1", "version": "4.3.1",
@ -2962,9 +3085,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"eslint": { "eslint": {
"version": "7.23.0", "version": "7.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.24.0.tgz",
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==", "integrity": "sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==",
"requires": { "requires": {
"@babel/code-frame": "7.12.11", "@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.0", "@eslint/eslintrc": "^0.4.0",
@ -3532,9 +3655,9 @@
"integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs="
}, },
"globals": { "globals": {
"version": "13.7.0", "version": "13.8.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz",
"integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==",
"requires": { "requires": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
}, },
@ -5245,9 +5368,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.44.0", "version": "2.45.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.44.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.45.2.tgz",
"integrity": "sha512-rGSF4pLwvuaH/x4nAS+zP6UNn5YUDWf/TeEU5IoXSZKBbKRNTCI3qMnYXKZgrC0D2KzS2baiOZt1OlqhMu5rnQ==", "integrity": "sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==",
"requires": { "requires": {
"fsevents": "~2.3.1" "fsevents": "~2.3.1"
} }
@ -5764,9 +5887,9 @@
} }
}, },
"table": { "table": {
"version": "6.0.8", "version": "6.0.9",
"resolved": "https://registry.npmjs.org/table/-/table-6.0.8.tgz", "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz",
"integrity": "sha512-OBAdezyozae8IvjHGXBDHByVkLCcsmffXUSj8LXkNb0SluRd4ug3GFCjk6JynZONIPhOkyr0Nnvbq1rlIspXyQ==", "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==",
"requires": { "requires": {
"ajv": "^8.0.1", "ajv": "^8.0.1",
"is-boolean-object": "^1.1.0", "is-boolean-object": "^1.1.0",
@ -5780,9 +5903,9 @@
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "8.0.1", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.1.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz",
"integrity": "sha512-46ZA4TalFcLLqX1dEU3dhdY38wAtDydJ4e7QQTVekLUTzXkb1LfqU6VOBXC/a9wiv4T094WURqJH6ZitF92Kqw==", "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",

View File

@ -41,9 +41,9 @@
"@babel/preset-env": "^7.13.15", "@babel/preset-env": "^7.13.15",
"@babel/preset-typescript": "^7.13.0", "@babel/preset-typescript": "^7.13.0",
"@fortawesome/fontawesome-free": "^5.15.3", "@fortawesome/fontawesome-free": "^5.15.3",
"@lingui/cli": "^3.8.6", "@lingui/cli": "^3.8.9",
"@lingui/core": "^3.8.6", "@lingui/core": "^3.8.9",
"@lingui/macro": "^3.8.6", "@lingui/macro": "^3.8.9",
"@patternfly/patternfly": "^4.96.2", "@patternfly/patternfly": "^4.96.2",
"@polymer/iron-form": "^3.0.1", "@polymer/iron-form": "^3.0.1",
"@polymer/paper-input": "^3.2.1", "@polymer/paper-input": "^3.2.1",
@ -53,18 +53,18 @@
"@sentry/browser": "^6.2.5", "@sentry/browser": "^6.2.5",
"@sentry/tracing": "^6.2.5", "@sentry/tracing": "^6.2.5",
"@types/chart.js": "^2.9.32", "@types/chart.js": "^2.9.32",
"@types/codemirror": "0.0.108", "@types/codemirror": "0.0.109",
"@types/grecaptcha": "^3.0.1", "@types/grecaptcha": "^3.0.1",
"@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.21.0", "@typescript-eslint/parser": "^4.22.0",
"authentik-api": "file:api", "authentik-api": "file:api",
"babel-plugin-macros": "^3.0.1", "babel-plugin-macros": "^3.0.1",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^3.0.2", "chart.js": "^3.1.1",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.60.0", "codemirror": "^5.60.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^2.4.16",
"eslint": "^7.23.0", "eslint": "^7.24.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-lit": "^1.3.0", "eslint-plugin-lit": "^1.3.0",
"flowchart.js": "^1.15.0", "flowchart.js": "^1.15.0",
@ -72,7 +72,7 @@
"lit-html": "^1.3.0", "lit-html": "^1.3.0",
"moment": "^2.29.1", "moment": "^2.29.1",
"rapidoc": "^9.0.0", "rapidoc": "^9.0.0",
"rollup": "^2.44.0", "rollup": "^2.45.2",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",

View File

@ -15,8 +15,10 @@ const extensions = [
const resources = [ const resources = [
{ src: "node_modules/rapidoc/dist/rapidoc-min.js", dest: "dist/" }, { src: "node_modules/rapidoc/dist/rapidoc-min.js", dest: "dist/" },
{ src: "node_modules/@patternfly/patternfly/patternfly.min.css", dest: "dist/" }, { src: "node_modules/@patternfly/patternfly/patternfly-base.css", dest: "dist/" },
{ src: "node_modules/@patternfly/patternfly/patternfly.min.css.map", dest: "dist/" }, { src: "node_modules/@patternfly/patternfly/components/Page/page.css", dest: "dist/" },
{ src: "node_modules/@patternfly/patternfly/components/EmptyState/empty-state.css", dest: "dist/" },
{ src: "node_modules/@patternfly/patternfly/components/Spinner/spinner.css", dest: "dist/" },
{ src: "src/authentik.css", dest: "dist/" }, { src: "src/authentik.css", dest: "dist/" },
{ src: "node_modules/@patternfly/patternfly/assets/*", dest: "dist/assets/" }, { src: "node_modules/@patternfly/patternfly/assets/*", dest: "dist/assets/" },

View File

@ -1,8 +1,4 @@
import * as Sentry from "@sentry/browser"; import { Configuration, Middleware, ResponseContext } from "authentik-api";
import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors";
import { Config, Configuration, Middleware, ResponseContext, RootApi } from "authentik-api";
import { getCookie } from "../utils"; import { getCookie } from "../utils";
import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer"; import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer";
import { MessageMiddleware } from "../elements/messages/Middleware"; import { MessageMiddleware } from "../elements/messages/Middleware";
@ -13,13 +9,13 @@ export class LoggingMiddleware implements Middleware {
console.debug(`authentik/api: ${context.response.status} ${context.init.method} ${context.url}`); console.debug(`authentik/api: ${context.response.status} ${context.init.method} ${context.url}`);
return Promise.resolve(context.response); return Promise.resolve(context.response);
} }
} }
export const DEFAULT_CONFIG = new Configuration({ export const DEFAULT_CONFIG = new Configuration({
basePath: "/api/v2beta", basePath: "/api/v2beta",
headers: { headers: {
"X-CSRFToken": getCookie("authentik_csrf"), "X-CSRFToken": getCookie("authentik_csrf"),
"X-Authentik-Prevent-Basic": "true"
}, },
middleware: [ middleware: [
API_DRAWER_MIDDLEWARE, API_DRAWER_MIDDLEWARE,
@ -27,27 +23,3 @@ export const DEFAULT_CONFIG = new Configuration({
new LoggingMiddleware(), new LoggingMiddleware(),
], ],
}); });
export function configureSentry(): Promise<Config> {
return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
if (config.errorReportingEnabled) {
Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`,
integrations: [
new Integrations.BrowserTracing(),
],
tracesSampleRate: 0.6,
environment: config.errorReportingEnvironment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
return event;
},
});
console.debug("authentik/config: Sentry enabled.");
}
return config;
});
}

50
web/src/api/Sentry.ts Normal file
View File

@ -0,0 +1,50 @@
import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors";
import { Config, RootApi } from "authentik-api";
import { me } from "./Users";
import { DEFAULT_CONFIG } from "./Config";
export function configureSentry(): Promise<Config> {
return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
if (config.errorReportingEnabled) {
Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`,
integrations: [
new Integrations.BrowserTracing({
tracingOrigins: [window.location.host, "localhost"],
}),
],
tracesSampleRate: 0.6,
environment: config.errorReportingEnvironment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (event.exception) {
me().then(user => {
Sentry.showReportDialog({
eventId: event.event_id,
user: {
email: user.user.email,
name: user.user.name,
}
});
});
}
return event;
},
});
console.debug("authentik/config: Sentry enabled.");
if (config.errorReportingSendPii) {
me().then(user => {
Sentry.setUser({ email: user.user.email });
console.debug("authentik/config: Sentry with PII enabled.");
});
}
}
return config;
});
}

View File

@ -1,5 +1,13 @@
:root { :root {
--ak-accent: #fd4b2d; --ak-accent: #fd4b2d;
--ak-dark-foreground: #fafafa;
--ak-dark-foreground-darker: #bebebe;
--ak-dark-foreground-link: #5a5cb9;
--ak-dark-background: #18191a;
--ak-dark-background-darker: #000000;
--ak-dark-background-light: #1c1e21;
--ak-dark-background-lighter: #2b2e33;
} }
html { html {
@ -89,17 +97,12 @@ html > form > input {
body { body {
background-color: var(--ak-dark-background) !important; background-color: var(--ak-dark-background) !important;
} }
.ak-initial-load h1 {
color: var(--ak-dark-foreground);
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--ak-dark-foreground: #fafafa;
--ak-dark-foreground-darker: #bebebe;
--ak-dark-foreground-link: #5a5cb9;
--ak-dark-background: #18191a;
--ak-dark-background-darker: #000000;
--ak-dark-background-light: #1c1e21;
--ak-dark-background-lighter: #2b2e33;
--pf-global--Color--100: var(--ak-dark-foreground); --pf-global--Color--100: var(--ak-dark-foreground);
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
--pf-global--link--Color: var(--ak-dark-foreground-link); --pf-global--link--Color: var(--ak-dark-foreground-link);
@ -279,6 +282,7 @@ body {
} }
.pf-c-notification-drawer__header { .pf-c-notification-drawer__header {
background-color: var(--ak-dark-background-lighter); background-color: var(--ak-dark-background-lighter);
color: var(--ak-dark-foreground);
} }
/* data list */ /* data list */
.pf-c-data-list__item { .pf-c-data-list__item {

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger"; export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2021.4.1-rc1"; export const VERSION = "2021.4.3";
export const PAGE_SIZE = 20; export const PAGE_SIZE = 20;
export const EVENT_REFRESH = "ak-refresh"; export const EVENT_REFRESH = "ak-refresh";
export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle";

View File

@ -39,12 +39,20 @@ export class PageHeader extends LitElement {
:host { :host {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
min-height: 114px;
} }
button.sidebar-trigger { button.sidebar-trigger {
background-color: var(--pf-c-page__main-section--m-light--BackgroundColor); background-color: var(--pf-c-page__main-section--m-light--BackgroundColor);
border-radius: 0px;
} }
.pf-c-page__main-section { .pf-c-page__main-section {
width: 100%; width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
img.pf-icon {
max-height: 24px;
} }
`]; `];
} }

View File

@ -59,7 +59,7 @@ export class ObjectChangelog extends Table<Event> {
${t`On behalf of ${item.user.on_behalf_of.username}`} ${t`On behalf of ${item.user.on_behalf_of.username}`}
</small>` : html``}`, </small>` : html``}`,
html`<span>${item.created?.toLocaleString()}</span>`, html`<span>${item.created?.toLocaleString()}</span>`,
html`<span>${item.clientIp}</span>`, html`<span>${item.clientIp || "-"}</span>`,
]; ];
} }
@ -76,7 +76,7 @@ export class ObjectChangelog extends Table<Event> {
} }
renderEmpty(): TemplateResult { renderEmpty(): TemplateResult {
return super.renderEmpty(html`<ak-empty-state header=${t`No Events found.`} icon="pf-icon-module"> return super.renderEmpty(html`<ak-empty-state header=${t`No Events found.`}>
<div slot="body"> <div slot="body">
${t`No matching events could be found.`} ${t`No matching events could be found.`}
</div> </div>

View File

@ -0,0 +1,77 @@
import { t } from "@lingui/macro";
import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client";
import { Table, TableColumn } from "../table/Table";
import { Event, EventsApi } from "authentik-api";
import "../forms/DeleteForm";
import "../Tabs";
import "../buttons/ModalButton";
import "../buttons/SpinnerButton";
import "../buttons/Dropdown";
import "../../pages/events/EventInfo";
import { PAGE_SIZE } from "../../constants";
import { DEFAULT_CONFIG } from "../../api/Config";
import { EventWithContext } from "../../api/Events";
@customElement("ak-events-user")
export class ObjectChangelog extends Table<Event> {
expandable = true;
@property()
order = "-created";
@property()
targetUser!: string;
apiEndpoint(page: number): Promise<AKResponse<Event>> {
return new EventsApi(DEFAULT_CONFIG).eventsEventsList({
page: page,
ordering: this.order,
pageSize: PAGE_SIZE / 2,
username: this.targetUser
});
}
columns(): TableColumn[] {
return [
new TableColumn(t`Action`, "action"),
new TableColumn(t`User`, "enabled"),
new TableColumn(t`Creation Date`, "created"),
new TableColumn(t`Client IP`, "client_ip"),
];
}
row(item: EventWithContext): TemplateResult[] {
return [
html`${item.action}`,
html`<div>${item.user?.username}</div>
${item.user.on_behalf_of ? html`<small>
${t`On behalf of ${item.user.on_behalf_of.username}`}
</small>` : html``}`,
html`<span>${item.created?.toLocaleString()}</span>`,
html`<span>${item.clientIp || "-"}</span>`,
];
}
renderExpanded(item: Event): TemplateResult {
return html`
<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<ak-event-info .event=${item as EventWithContext}></ak-event-info>
</div>
</td>
<td></td>
<td></td>
<td></td>`;
}
renderEmpty(): TemplateResult {
return super.renderEmpty(html`<ak-empty-state header=${t`No Events found.`}>
<div slot="body">
${t`No matching events could be found.`}
</div>
</ak-empty-state>`);
}
}

View File

@ -35,7 +35,9 @@ export class UserOAuthCodeList extends Table<ExpiringBaseGrantModel> {
row(item: ExpiringBaseGrantModel): TemplateResult[] { row(item: ExpiringBaseGrantModel): TemplateResult[] {
return [ return [
html`${item.provider.name}`, html`<a href="#/core/providers/${item.provider?.pk}">
${item.provider?.name}
</a>`,
html`${item.expires?.toLocaleString()}`, html`${item.expires?.toLocaleString()}`,
html`${item.scope.join(", ")}`, html`${item.scope.join(", ")}`,
html` html`

View File

@ -35,7 +35,9 @@ export class UserOAuthRefreshList extends Table<ExpiringBaseGrantModel> {
row(item: ExpiringBaseGrantModel): TemplateResult[] { row(item: ExpiringBaseGrantModel): TemplateResult[] {
return [ return [
html`${item.provider.name}`, html`<a href="#/core/providers/${item.provider?.pk}">
${item.provider?.name}
</a>`,
html`${item.expires?.toLocaleString()}`, html`${item.expires?.toLocaleString()}`,
html`${item.scope.join(", ")}`, html`${item.scope.join(", ")}`,
html` html`

View File

@ -33,7 +33,12 @@ export class RouterOutlet extends LitElement {
css` css`
:host { :host {
height: 100vh; height: 100vh;
background-color: var(--ak-dark-background, var(--pf-c-page--BackgroundColor)) !important; background-color: var(--pf-global--BackgroundColor--light-300) !important;
}
@media (prefers-color-scheme: dark) {
:host {
background-color: var(--ak-dark-background) !important;
}
} }
*:first-child { *:first-child {
height: 100%; height: 100%;

View File

@ -103,6 +103,7 @@ export class Sidebar extends LitElement {
css` css`
:host { :host {
z-index: 100; z-index: 100;
box-shadow: none !important;
} }
.pf-c-nav__link.pf-m-current::after, .pf-c-nav__link.pf-m-current::after,
.pf-c-nav__link.pf-m-current:hover::after, .pf-c-nav__link.pf-m-current:hover::after,

View File

@ -1,7 +1,7 @@
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
import { configureSentry } from "../../api/Config"; import { configureSentry } from "../../api/Sentry";
import { Config } from "authentik-api"; import { Config } from "authentik-api";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";

View File

@ -162,11 +162,11 @@ export class FlowExecutor extends LitElement implements StageHost {
</div> </div>
<footer class="pf-c-login__main-footer"> <footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"> <ul class="pf-c-login__main-footer-links">
<li class="pf-c-login__main-footer-links-item"> <li class="pf-c-login__main-footer-links-item">
<a class="pf-c-button pf-m-primary pf-m-block" href="/"> <a class="pf-c-button pf-m-primary pf-m-block" href="/">
${t`Return`} ${t`Return`}
</a> </a>
</li> </li>
</ul> </ul>
</footer>` </footer>`
}; };

View File

@ -2,7 +2,6 @@ import { css, CSSResult, html, LitElement, property, TemplateResult } from "lit-
import { SidebarItem } from "../elements/sidebar/Sidebar"; import { SidebarItem } from "../elements/sidebar/Sidebar";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFSkipToContent from "@patternfly/patternfly/components/SkipToContent/skip-to-content.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
@ -13,7 +12,9 @@ import "../elements/Banner";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { me } from "../api/Users"; import { me } from "../api/Users";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE } from "../constants"; import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants";
import { AdminApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../api/Config";
export abstract class Interface extends LitElement { export abstract class Interface extends LitElement {
@property({type: Boolean}) @property({type: Boolean})
@ -25,7 +26,7 @@ export abstract class Interface extends LitElement {
abstract get sidebar(): SidebarItem[]; abstract get sidebar(): SidebarItem[];
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFPage, PFSkipToContent, PFButton, PFDrawer, css` return [PFBase, PFPage, PFButton, PFDrawer, css`
.pf-c-page__main, .pf-c-drawer__content, .pf-c-page__drawer { .pf-c-page__main, .pf-c-drawer__content, .pf-c-page__drawer {
z-index: auto !important; z-index: auto !important;
} }
@ -48,6 +49,17 @@ export abstract class Interface extends LitElement {
render(): TemplateResult { render(): TemplateResult {
return html` return html`
${until(new AdminApi(DEFAULT_CONFIG).adminVersionList().then(version => {
if (version.versionCurrent !== VERSION) {
return html`<ak-banner>
${t`A newer version of the frontend is available.`}
<button @click=${() => { window.location.reload(); }}>
${t`Reload`}
</button>
</ak-banner>`;
}
return html``;
}))}
${until(me().then((u) => { ${until(me().then((u) => {
if (u.original) { if (u.original) {
return html`<ak-banner> return html`<ak-banner>

View File

@ -5,16 +5,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png"> <link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png">
<link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png"> <link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly.min.css"> <link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
<link rel="stylesheet" type="text/css" href="/static/dist/page.css">
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css">
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css">
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css"> <link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
<script src="/static/dist/poly.js" type="module"></script> <script src="/static/dist/poly.js" type="module"></script>
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
<script src="/static/dist/AdminInterface.js" type="module"></script> <script src="/static/dist/AdminInterface.js" type="module"></script>
<title>authentik</title> <title>authentik</title>
</head> </head>
<body> <body>
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-interface-admin> <ak-interface-admin>
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;"> <div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="Loading..."> <span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="Loading...">

View File

@ -5,16 +5,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png"> <link rel="icon" type="image/png" href="/static/dist/assets/icons/icon.png">
<link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png"> <link rel="shortcut icon" type="image/png" href="/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly.min.css"> <link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
<link rel="stylesheet" type="text/css" href="/static/dist/page.css">
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css">
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css">
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css"> <link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
<script src="/static/dist/poly.js" type="module"></script> <script src="/static/dist/poly.js" type="module"></script>
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
<script src="/static/dist/FlowInterface.js" type="module"></script> <script src="/static/dist/FlowInterface.js" type="module"></script>
<title>authentik</title> <title>authentik</title>
</head> </head>
<body> <body>
<ak-message-container></ak-message-container> <ak-message-container></ak-message-container>
<ak-flow-executor> <ak-flow-executor>
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"> <section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;"> <div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content"> <div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="Loading..."> <span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="Loading...">

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