Compare commits

...

51 Commits

Author SHA1 Message Date
e4b66d991c release: 0.13.2-stable 2020-12-17 20:20:47 +01:00
68adc2d5a5 admin: fix warning during swagger generation 2020-12-17 19:49:35 +01:00
349a3a67d5 flows: use to_stage_response in _flow_done() 2020-12-17 19:34:15 +01:00
e1394207e7 flows: fix inconsistent behaviour when flow is empty 2020-12-17 19:22:24 +01:00
f265c1f10b admin: fix cache clean views erroring 2020-12-17 19:03:32 +01:00
1aecdc7f8f web: fix css for policy tertiary buttons and text on flow card 2020-12-17 14:31:45 +01:00
a18edaf62b build(deps): bump @sentry/tracing from 5.29.0 to 5.29.1 in /web (#411)
Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 5.29.0 to 5.29.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/5.29.0...5.29.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 11:41:19 +01:00
c91abe448c build(deps): bump celery from 5.0.4 to 5.0.5 (#407)
Bumps [celery](https://github.com/celery/celery) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/celery/celery/releases)
- [Changelog](https://github.com/celery/celery/blob/master/Changelog.rst)
- [Commits](https://github.com/celery/celery/compare/v5.0.4...v5.0.5)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 09:46:11 +01:00
e531e52403 build(deps): bump django-storages from 1.10.1 to 1.11 (#408)
Bumps [django-storages](https://github.com/jschneier/django-storages) from 1.10.1 to 1.11.
- [Release notes](https://github.com/jschneier/django-storages/releases)
- [Changelog](https://github.com/jschneier/django-storages/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jschneier/django-storages/compare/1.10.1...1.11)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 09:45:58 +01:00
cae536fa65 build(deps): bump boto3 from 1.16.37 to 1.16.38 (#409)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.37 to 1.16.38.
- [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.16.37...1.16.38)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 09:45:45 +01:00
316b15b8a9 build(deps): bump @sentry/browser from 5.29.0 to 5.29.1 in /web (#410)
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.29.0 to 5.29.1.
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/5.29.0...5.29.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 09:45:29 +01:00
e6ccd4fa76 web: fix file name casing 2020-12-17 00:18:24 +01:00
86aabba3ed web: fix file name casing 2020-12-17 00:18:03 +01:00
0b36aad5c8 admin: ensure clean_expired_models is called during tests 2020-12-17 00:17:20 +01:00
64d2a216f0 web: fix linting 2020-12-16 23:50:23 +01:00
a5e5e140d6 admin: add full api tests 2020-12-16 23:42:44 +01:00
29f98abd00 root: update swagger 2020-12-16 23:32:14 +01:00
7b5ce4e98a web: use colours for icons, move users to separate card 2020-12-16 23:28:04 +01:00
d7fa52ebf3 admin: remove old admin overview 2020-12-16 23:21:38 +01:00
2ffaa94825 web: fix typo 2020-12-16 23:08:40 +01:00
b80b2626a6 web: fix rendering of version 2020-12-16 23:08:35 +01:00
3b7bba5a62 web: make sure naming matches backend 2020-12-16 23:03:06 +01:00
2d9efe035e web: migrate admin overview cards to separate files 2020-12-16 23:00:32 +01:00
48438e28fd admin: separate overview API into WorkerAPI and VersionAPI 2020-12-16 22:53:53 +01:00
885a2f0a58 web: add flow and policy cache card 2020-12-16 22:30:37 +01:00
cf46ee06b7 api: create dedicated api for cached flows and policies 2020-12-16 22:18:36 +01:00
9e33b49d29 web: rewrite aggregate cards to separate components 2020-12-16 22:00:40 +01:00
1179ba4ef2 api: remove counters from overview api and allow filtering on object apis 2020-12-16 22:00:29 +01:00
3c12c8b3ff core: make Provider SerializerModel 2020-12-16 21:38:40 +01:00
4d22659b6e web: re-organise sidebar 2020-12-16 16:04:11 +01:00
2c0709eeee web: render SidebarItem from the item 2020-12-16 16:04:02 +01:00
c24d1b6b84 outposts: fix incorrect timeout for state cache 2020-12-16 12:14:34 +01:00
040e148a73 release: 0.13.1-stable 2020-12-16 11:26:15 +01:00
b85d550ee0 build(deps-dev): bump pytest from 6.2.0 to 6.2.1 (#405)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...6.2.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-16 09:15:14 +01:00
ce95139d66 build(deps): bump boto3 from 1.16.36 to 1.16.37 (#404)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.36 to 1.16.37.
- [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.16.36...1.16.37)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-16 09:14:58 +01:00
46436a5780 build(deps): bump @types/chart.js from 2.9.28 to 2.9.29 in /web (#406)
Bumps [@types/chart.js](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chart.js) from 2.9.28 to 2.9.29.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chart.js)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-16 09:14:43 +01:00
835a9aaaf2 outposts: fix circular import 2020-12-16 00:00:36 +01:00
42005e7def outposts: ensure all Service Connection state updates are done by the task 2020-12-15 23:39:52 +01:00
d9956e1e9c outpost: fix invalid incluster config causing Outpost Service Connection list to fail 2020-12-15 21:17:33 +01:00
4b1e73251a root: fix messages showing for all sessions of a user 2020-12-15 15:19:15 +01:00
736dbdca33 build(deps-dev): bump @rollup/plugin-typescript in /web (#401)
Bumps [@rollup/plugin-typescript](https://github.com/rollup/plugins) from 8.0.0 to 8.1.0.
- [Release notes](https://github.com/rollup/plugins/releases)
- [Commits](https://github.com/rollup/plugins/compare/eslint-v8.0.0...typescript-v8.1.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 10:54:31 +01:00
789b8e5d3e build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#402)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.9.1 to 4.10.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.10.0/packages/eslint-plugin)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 10:28:11 +01:00
074b55f66b build(deps): bump boto3 from 1.16.35 to 1.16.36 (#398)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.35 to 1.16.36.
- [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.16.35...1.16.36)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 08:59:52 +01:00
d9bc5ea4d1 build(deps): bump rollup from 2.34.2 to 2.35.1 in /web (#399)
Bumps [rollup](https://github.com/rollup/rollup) from 2.34.2 to 2.35.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.34.2...v2.35.1)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 08:59:42 +01:00
716bb9f188 build(deps): bump @patternfly/patternfly from 4.65.6 to 4.70.2 in /web (#400)
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 4.65.6 to 4.70.2.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/patternfly/patternfly/compare/prerelease-v4.65.6...prerelease-v4.70.2)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 08:59:30 +01:00
dd496619a2 build(deps-dev): bump @typescript-eslint/parser in /web (#403)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.9.1 to 4.10.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.10.0/packages/parser)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-15 08:59:04 +01:00
51d07f7913 proxy: output JSON logs 2020-12-14 19:41:32 +01:00
5c4163579b root: fix application icons now showing with docker-compose 2020-12-14 19:32:48 +01:00
5a73413d58 web: fix brand not showing on firefox 2020-12-14 19:26:02 +01:00
51a5d4bf49 docs: fix issues when overscrolling 2020-12-14 14:16:00 +01:00
8bbb854073 root: make docker-compose database name and username configurable 2020-12-14 12:27:33 +01:00
84 changed files with 1173 additions and 927 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.13.0-stable current_version = 0.13.2-stable
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:0.13.0-stable -t beryju/authentik:0.13.2-stable
-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:0.13.0-stable run: docker push beryju/authentik:0.13.2-stable
- 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 proxy/ cd proxy/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-proxy:0.13.0-stable \ -t beryju/authentik-proxy:0.13.2-stable \
-t beryju/authentik-proxy:latest \ -t beryju/authentik-proxy:latest \
-f Dockerfile . -f Dockerfile .
- name: Push Docker Container to Registry (versioned) - name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:0.13.0-stable run: docker push beryju/authentik-proxy:0.13.2-stable
- 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:
@ -69,11 +69,11 @@ jobs:
cd web/ cd web/
docker build \ docker build \
--no-cache \ --no-cache \
-t beryju/authentik-static:0.13.0-stable \ -t beryju/authentik-static:0.13.2-stable \
-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:0.13.0-stable run: docker push beryju/authentik-static:0.13.2-stable
- 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:
@ -107,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
tagName: 0.13.0-stable tagName: 0.13.2-stable
environment: beryjuorg-prod environment: beryjuorg-prod

54
Pipfile.lock generated
View File

@ -53,10 +53,10 @@
}, },
"autobahn": { "autobahn": {
"hashes": [ "hashes": [
"sha256:74ca21d3552825615a65d47ec38d0aa5961a1345f7639f5b0e2abfff40cbfd07", "sha256:491238c31f78721eaa9d0593909ab455a4ea68127aadd76ecf67185143f5f298",
"sha256:85c14b4a404146339ffd171e1ea1f65bf71e2f777d810aaa8a36119273869e3d" "sha256:72b68a1ce1e10e3cbcc3b280aae86d5b2e7a1f409febab1ab91a8a3274113f6e"
], ],
"version": "==20.12.1" "version": "==20.12.2"
}, },
"automat": { "automat": {
"hashes": [ "hashes": [
@ -74,18 +74,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:2a6e92194bd6f2341908dc9b133af057ea1ff20b7d7e54674f48cdb531d93ca5", "sha256:18d7ba5d623d4794f439201ab900c9c14a50019bc52d9113b0a2bb2e1ef9af2c",
"sha256:a35e0915547ea659ddd832c9aaf55038c56fa894c4cc2a2a46cd6c642494012a" "sha256:1ddfd307d409e7bc792bd12923078f59c2f56fbba4065c320b3f768481bbbbf7"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.16.35" "version": "==1.16.38"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:633aa910509b060717df4130f7e2841f1101c0c47fd5871f4903b4b1dbab7e23", "sha256:1f1ecb1b0c6ffc8fcdd5eeb40f33e986dfe9724dc66c83017014a0506af6378a",
"sha256:d31dce56799edb5796085d5296931faae201e28e14e568d9db4dac237a135fe3" "sha256:38ccc132c5b9d1e7a4dd37af78061fd2dd0e4fd611f527b409a4e9a679a85cdb"
], ],
"version": "==1.19.35" "version": "==1.19.38"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -96,11 +96,11 @@
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
"sha256:45bb7909061862305cefec94289fabc1b89ac004680f4dc7d9dea642a2507e53", "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13",
"sha256:533f3635065b7ed362ffc04228635b4c82d53a9ab812118ccdedb5eae281fb97" "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.0.4" "version": "==5.0.5"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -168,10 +168,10 @@
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"version": "==3.0.4" "version": "==4.0.0"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -343,11 +343,11 @@
}, },
"django-storages": { "django-storages": {
"hashes": [ "hashes": [
"sha256:12de8fb2605b9b57bfaf54b075280d7cbb3b3ee1ca4bc9b9add147af87fe3a2c", "sha256:056ec3e9e2b0c6f363913976072ffba2923e79e4859578047da139ba1637497e",
"sha256:652275ab7844538c462b62810276c0244866f345878256a9e0e86f5b1283ae18" "sha256:7af56611c62a1c174aab4e862efb7fdd98296dccf76f42135f5b6851fc313c97"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.10.1" "version": "==1.11"
}, },
"djangorestframework": { "djangorestframework": {
"hashes": [ "hashes": [
@ -875,10 +875,10 @@
}, },
"pyopenssl": { "pyopenssl": {
"hashes": [ "hashes": [
"sha256:898aefbde331ba718570244c3b01dcddb1b31a3b336613436a45e52e27d9a82d", "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51",
"sha256:92f08eccbd73701cf744e8ffd6989aa7842d48cbe3fea8a7c031c5647f590ac5" "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"
], ],
"version": "==20.0.0" "version": "==20.0.1"
}, },
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
@ -950,10 +950,10 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"version": "==2.25.0" "version": "==2.25.1"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
@ -1586,11 +1586,11 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:b12e09409c5bdedc28d308469e156127004a436b41e9b44f9bff6446cbab9152", "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
"sha256:d69e1a80b34fe4d596c9142f35d9e523d98a2838976f1a68419a8f051b24cec6" "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.2.0" "version": "==6.2.1"
}, },
"pytest-django": { "pytest-django": {
"hashes": [ "hashes": [

View File

@ -1,2 +1,2 @@
"""authentik""" """authentik"""
__version__ = "0.13.0-stable" __version__ = "0.13.2-stable"

View File

@ -1,4 +1,4 @@
"""authentik administration overview""" """authentik administration metrics"""
import time import time
from collections import Counter from collections import Counter
from datetime import timedelta from datetime import timedelta
@ -47,7 +47,7 @@ def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]:
class AdministrationMetricsSerializer(Serializer): class AdministrationMetricsSerializer(Serializer):
"""Overview View""" """Login Metrics per 1h"""
logins_per_1h = SerializerMethodField() logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField() logins_failed_per_1h = SerializerMethodField()
@ -68,12 +68,12 @@ class AdministrationMetricsSerializer(Serializer):
class AdministrationMetricsViewSet(ViewSet): class AdministrationMetricsViewSet(ViewSet):
"""Return single instance of AdministrationMetricsSerializer""" """Login Metrics per 1h"""
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)}) @swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""Return single instance of AdministrationMetricsSerializer""" """Login Metrics per 1h"""
serializer = AdministrationMetricsSerializer(True) serializer = AdministrationMetricsSerializer(True)
return Response(serializer.data) return Response(serializer.data)

View File

@ -1,79 +0,0 @@
"""authentik administration overview"""
from django.core.cache import cache
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik import __version__
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.models import Provider
from authentik.policies.models import Policy
from authentik.root.celery import CELERY_APP
class AdministrationOverviewSerializer(Serializer):
"""Overview View"""
version = SerializerMethodField()
version_latest = SerializerMethodField()
worker_count = SerializerMethodField()
providers_without_application = SerializerMethodField()
policies_without_binding = SerializerMethodField()
cached_policies = SerializerMethodField()
cached_flows = SerializerMethodField()
def get_version(self, _) -> str:
"""Get current version"""
return __version__
def get_version_latest(self, _) -> str:
"""Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache:
update_latest_version.delay()
return __version__
return version_in_cache
def get_worker_count(self, _) -> int:
"""Ping workers"""
return len(CELERY_APP.control.ping(timeout=0.5))
def get_providers_without_application(self, _) -> int:
"""Count of providers without application"""
return len(Provider.objects.filter(application=None))
def get_policies_without_binding(self, _) -> int:
"""Count of policies not bound or use in prompt stages"""
return len(
Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True)
)
def get_cached_policies(self, _) -> int:
"""Get cached policy count"""
return len(cache.keys("policy_*"))
def get_cached_flows(self, _) -> int:
"""Get cached flow count"""
return len(cache.keys("flow_*"))
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class AdministrationOverviewViewSet(ViewSet):
"""Return single instance of AdministrationOverviewSerializer"""
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AdministrationOverviewSerializer(many=True)})
def list(self, request: Request) -> Response:
"""Return single instance of AdministrationOverviewSerializer"""
serializer = AdministrationOverviewSerializer(True)
return Response(serializer.data)

View File

@ -66,7 +66,7 @@ class TaskViewSet(ViewSet):
"successful": True, "successful": True,
} }
) )
except ImportError: except ImportError: # pragma: no cover
# if we get an import error, the module path has probably changed # if we get an import error, the module path has probably changed
task.delete() task.delete()
return Response({"successful": False}) return Response({"successful": False})

View File

@ -0,0 +1,60 @@
"""authentik administration overview"""
from django.core.cache import cache
from drf_yasg2.utils import swagger_auto_schema
from packaging.version import parse
from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import GenericViewSet
from authentik import __version__
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
class VersionSerializer(Serializer):
"""Get running and latest version."""
version_current = SerializerMethodField()
version_latest = SerializerMethodField()
outdated = SerializerMethodField()
def get_version_current(self, _) -> str:
"""Get current version"""
return __version__
def get_version_latest(self, _) -> str:
"""Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: # pragma: no cover
update_latest_version.delay()
return __version__
return version_in_cache
def get_outdated(self, instance) -> bool:
"""Check if we're running the latest version"""
return parse(self.get_version_current(instance)) < parse(
self.get_version_latest(instance)
)
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class VersionViewSet(ListModelMixin, GenericViewSet):
"""Get running and latest version."""
permission_classes = [IsAdminUser]
def get_queryset(self):
return None
@swagger_auto_schema(responses={200: VersionSerializer(many=True)})
def list(self, request: Request) -> Response:
"""Get running and latest version."""
return Response(VersionSerializer(True).data)

View File

@ -0,0 +1,25 @@
"""authentik administration overview"""
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import GenericViewSet
from authentik.root.celery import CELERY_APP
class WorkerViewSet(ListModelMixin, GenericViewSet):
"""Get currently connected worker count."""
serializer_class = Serializer
permission_classes = [IsAdminUser]
def get_queryset(self):
return None
def list(self, request: Request) -> Response:
"""Get currently connected worker count."""
return Response(
{"pagination": {"count": len(CELERY_APP.control.ping(timeout=0.5))}}
)

View File

@ -1,230 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load static %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>{% trans 'System Overview' %}</h1>
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-l-gallery pf-m-gutter">
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 3;grid-row-end: span 2;">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Logins over the last 24 hours' %}
</div>
</div>
<div class="pf-c-card__body">
<ak-admin-logins-chart url="{% url 'authentik_api:admin_metrics-list' %}"></ak-admin-logins-chart>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 2;grid-row-end: span 3;">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Apps with most usage' %}
</div>
</div>
<div class="pf-c-card__body">
<table class="pf-c-table pf-m-compact" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Application' %}</th>
<th role="columnheader" scope="col">{% trans 'Logins' %}</th>
<th role="columnheader" scope="col"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for app in most_used_applications %}
<tr role="row">
<td role="cell">
{{ app.application.name }}
</td>
<td role="cell">
{{ app.total_logins }}
</td>
<td role="cell">
<progress value="{{ app.total_logins }}" max="{{ most_used_applications.0.total_logins }}"></progress>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
</div>
<a href="{% url 'authentik_admin:providers' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div>
<div class="pf-c-card__body">
{% if providers_without_application.exists %}
<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ provider_count }}
</p>
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
{% else %}
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> {{ provider_count }}
</p>
{% endif %}
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
</div>
<a href="{% url 'authentik_admin:policies' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div>
<div class="pf-c-card__body">
{% if policies_without_binding %}
<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ policy_count }}
</p>
<p>{% trans 'Policies without binding exist.' %}</p>
{% else %}
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> {{ policy_count }}
</p>
{% endif %}
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
</div>
<a href="{% url 'authentik_admin:users' %}">
<i class="fa fa-external-link-alt"> </i>
</a>
</div>
<div class="pf-c-card__body">
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> {{ user_count }}
</p>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
</div>
<a href="https://github.com/BeryJu/authentik/releases" target="_blank">
<i class="fa fa-external-link-alt"> </i>
</a>
</div>
<div class="pf-c-card__body">
<p class="ak-aggregate-card">
{% if version >= version_latest %}
<i class="fa fa-check-circle"></i> {{ version }}
{% else %}
<i class="fa fa-exclamation-triangle"></i> {{ version }}
{% endif %}
</p>
{% if version >= version_latest %}
{% blocktrans %}
Up-to-date!
{% endblocktrans %}
{% else %}
{% blocktrans with latest=version_latest %}
{{ latest }} is available!
{% endblocktrans %}
{% endif %}
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %}
</div>
</div>
<fetch-fill-slot class="pf-c-card__body" url="{% url 'authentik_api:admin_overview-list' %}" key="worker_count">
<div slot="value < 1">
<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> <span data-value></span>
</p>
<p>{% trans 'No workers connected.' %}</p>
</div>
<div slot="value >= 1">
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> <span data-value></span>
</p>
</div>
<div>
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</fetch-fill-slot>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:overview-clear-policy-cache' %}">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>
</div>
<div class="pf-c-card__body">
{% if cached_policies < 1 %}
<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
</p>
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
{% else %}
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> {{ cached_policies }}
</p>
{% endif %}
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
</div>
<ak-modal-button href="{% url 'authentik_admin:overview-clear-flow-cache' %}">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>
</div>
<div class="pf-c-card__body">
{% if cached_flows < 1 %}
<p class="ak-aggregate-card">
<span class="fa fa-exclamation-triangle"></span> {{ cached_flows }}
</p>
<p>{% trans 'No flows cached.' %}</p>
{% else %}
<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> {{ cached_flows }}
</p>
{% endif %}
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -6,6 +6,7 @@ from django.test import TestCase
from authentik import __version__ from authentik import __version__
from authentik.core.models import Group, User from authentik.core.models import Group, User
from authentik.core.tasks import clean_expired_models
class TestAdminAPI(TestCase): class TestAdminAPI(TestCase):
@ -19,19 +20,54 @@ class TestAdminAPI(TestCase):
self.group.save() self.group.save()
self.client.force_login(self.user) self.client.force_login(self.user)
def test_overview(self): def test_tasks(self):
"""Test Overview API""" """Test Task API"""
response = self.client.get(reverse("authentik_api:admin_overview-list")) clean_expired_models.delay()
response = self.client.get(reverse("authentik_api:admin_system_tasks-list"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
body = loads(response.content) body = loads(response.content)
self.assertEqual(body["version"], __version__) self.assertTrue(
any([task["task_name"] == "clean_expired_models" for task in body])
)
def test_tasks_retry(self):
"""Test Task API (retry)"""
clean_expired_models.delay()
response = self.client.post(
reverse(
"authentik_api:admin_system_tasks-retry",
kwargs={"pk": "clean_expired_models"},
)
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertTrue(body["successful"])
def test_tasks_retry_404(self):
"""Test Task API (retry, 404)"""
response = self.client.post(
reverse(
"authentik_api:admin_system_tasks-retry",
kwargs={"pk": "qwerqewrqrqewrqewr"},
)
)
self.assertEqual(response.status_code, 404)
def test_version(self):
"""Test Version API"""
response = self.client.get(reverse("authentik_api:admin_version-list"))
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["version_current"], __version__)
def test_workers(self):
"""Test Workers API"""
response = self.client.get(reverse("authentik_api:admin_workers-list"))
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["pagination"]["count"], 0)
def test_metrics(self): def test_metrics(self):
"""Test metrics API""" """Test metrics API"""
response = self.client.get(reverse("authentik_api:admin_metrics-list")) response = self.client.get(reverse("authentik_api:admin_metrics-list"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_tasks(self):
"""Test tasks metrics API"""
response = self.client.get(reverse("authentik_api:admin_system_tasks-list"))
self.assertEqual(response.status_code, 200)

View File

@ -34,7 +34,6 @@ urlpatterns = [
overview.PolicyCacheClearView.as_view(), overview.PolicyCacheClearView.as_view(),
name="overview-clear-policy-cache", name="overview-clear-policy-cache",
), ),
path("overview/", overview.AdministrationOverviewView.as_view(), name="overview"),
# Applications # Applications
path( path(
"applications/", applications.ApplicationListView.as_view(), name="applications" "applications/", applications.ApplicationListView.as_view(), name="applications"

View File

@ -1,65 +1,25 @@
"""authentik administration overview""" """authentik administration overview"""
from typing import Union
from django.conf import settings
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache from django.core.cache import cache
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView, TemplateView from django.views.generic import FormView
from packaging.version import LegacyVersion, Version, parse
from structlog import get_logger from structlog import get_logger
from authentik import __version__
from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm
from authentik.admin.mixins import AdminRequiredMixin from authentik.admin.mixins import AdminRequiredMixin
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.models import Provider, User
from authentik.policies.models import Policy
LOGGER = get_logger() LOGGER = get_logger()
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
"""Overview View"""
template_name = "administration/overview.html"
def get_latest_version(self) -> Union[LegacyVersion, Version]:
"""Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache:
if not settings.DEBUG:
update_latest_version.delay()
return parse(__version__)
return parse(version_in_cache)
def get_context_data(self, **kwargs):
kwargs["policy_count"] = len(Policy.objects.all())
kwargs["user_count"] = len(User.objects.all()) - 1 # Remove anonymous user
kwargs["provider_count"] = len(Provider.objects.all())
kwargs["version"] = parse(__version__)
kwargs["version_latest"] = self.get_latest_version()
kwargs["providers_without_application"] = Provider.objects.filter(
application=None
)
kwargs["policies_without_binding"] = len(
Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True)
)
kwargs["cached_policies"] = len(cache.keys("policy_*"))
kwargs["cached_flows"] = len(cache.keys("flow_*"))
return super().get_context_data(**kwargs)
class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
"""View to clear Policy cache""" """View to clear Policy cache"""
form_class = PolicyCacheClearForm form_class = PolicyCacheClearForm
template_name = "generic/form_non_model.html" template_name = "generic/form_non_model.html"
success_url = reverse_lazy("authentik_admin:overview") success_url = "/"
success_message = _("Successfully cleared Policy cache") success_message = _("Successfully cleared Policy cache")
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
@ -75,7 +35,7 @@ class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
form_class = FlowCacheClearForm form_class = FlowCacheClearForm
template_name = "generic/form_non_model.html" template_name = "generic/form_non_model.html"
success_url = reverse_lazy("authentik_admin:overview") success_url = "/"
success_message = _("Successfully cleared Flow cache") success_message = _("Successfully cleared Flow cache")
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:

View File

@ -5,9 +5,10 @@ from drf_yasg2.views import get_schema_view
from rest_framework import routers from rest_framework import routers
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from authentik.admin.api.overview import AdministrationOverviewViewSet from authentik.admin.api.metrics import AdministrationMetricsViewSet
from authentik.admin.api.overview_metrics import AdministrationMetricsViewSet
from authentik.admin.api.tasks import TaskViewSet from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionViewSet
from authentik.admin.api.workers import WorkerViewSet
from authentik.api.v2.config import ConfigsViewSet from authentik.api.v2.config import ConfigsViewSet
from authentik.api.v2.messages import MessagesViewSet from authentik.api.v2.messages import MessagesViewSet
from authentik.audit.api import EventViewSet from authentik.audit.api import EventViewSet
@ -19,13 +20,22 @@ from authentik.core.api.sources import SourceViewSet
from authentik.core.api.tokens import TokenViewSet from authentik.core.api.tokens import TokenViewSet
from authentik.core.api.users import UserViewSet from authentik.core.api.users import UserViewSet
from authentik.crypto.api import CertificateKeyPairViewSet from authentik.crypto.api import CertificateKeyPairViewSet
from authentik.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet from authentik.flows.api import (
FlowCacheViewSet,
FlowStageBindingViewSet,
FlowViewSet,
StageViewSet,
)
from authentik.outposts.api import ( from authentik.outposts.api import (
DockerServiceConnectionViewSet, DockerServiceConnectionViewSet,
KubernetesServiceConnectionViewSet, KubernetesServiceConnectionViewSet,
OutpostViewSet, OutpostViewSet,
) )
from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet from authentik.policies.api import (
PolicyBindingViewSet,
PolicyCacheViewSet,
PolicyViewSet,
)
from authentik.policies.dummy.api import DummyPolicyViewSet from authentik.policies.dummy.api import DummyPolicyViewSet
from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet
from authentik.policies.expression.api import ExpressionPolicyViewSet from authentik.policies.expression.api import ExpressionPolicyViewSet
@ -63,9 +73,8 @@ router = routers.DefaultRouter()
router.register("root/messages", MessagesViewSet, basename="messages") router.register("root/messages", MessagesViewSet, basename="messages")
router.register("root/config", ConfigsViewSet, basename="configs") router.register("root/config", ConfigsViewSet, basename="configs")
router.register( router.register("admin/version", VersionViewSet, basename="admin_version")
"admin/overview", AdministrationOverviewViewSet, basename="admin_overview" router.register("admin/workers", WorkerViewSet, basename="admin_workers")
)
router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics") router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics")
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
@ -82,6 +91,7 @@ router.register(
router.register("outposts/proxy", ProxyOutpostConfigViewSet) router.register("outposts/proxy", ProxyOutpostConfigViewSet)
router.register("flows/instances", FlowViewSet) router.register("flows/instances", FlowViewSet)
router.register("flows/cached", FlowCacheViewSet, basename="flows_cache")
router.register("flows/bindings", FlowStageBindingViewSet) router.register("flows/bindings", FlowStageBindingViewSet)
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
@ -94,6 +104,7 @@ router.register("sources/saml", SAMLSourceViewSet)
router.register("sources/oauth", OAuthSourceViewSet) router.register("sources/oauth", OAuthSourceViewSet)
router.register("policies/all", PolicyViewSet) router.register("policies/all", PolicyViewSet)
router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache")
router.register("policies/bindings", PolicyBindingViewSet) router.register("policies/bindings", PolicyBindingViewSet)
router.register("policies/expression", ExpressionPolicyViewSet) router.register("policies/expression", ExpressionPolicyViewSet)
router.register("policies/group_membership", GroupMembershipPolicyViewSet) router.register("policies/group_membership", GroupMembershipPolicyViewSet)

View File

@ -11,7 +11,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.admin.api.overview_metrics import get_events_per_1h from authentik.admin.api.metrics import get_events_per_1h
from authentik.audit.models import EventAction from authentik.audit.models import EventAction
from authentik.core.models import Application from authentik.core.models import Application
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine

View File

@ -1,6 +1,6 @@
"""Provider API Views""" """Provider API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.models import Provider from authentik.core.models import Provider
@ -14,17 +14,33 @@ class ProviderSerializer(ModelSerializer):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("provider", "") return obj._meta.object_name.lower().replace("provider", "")
def to_representation(self, instance: Provider):
# pyright: reportGeneralTypeIssues=false
if instance.__class__ == Provider:
return super().to_representation(instance)
return instance.serializer(instance=instance).data
class Meta: class Meta:
model = Provider model = Provider
fields = ["pk", "name", "authorization_flow", "property_mappings", "__type__"] fields = [
"pk",
"name",
"application",
"authorization_flow",
"property_mappings",
"__type__",
]
class ProviderViewSet(ReadOnlyModelViewSet): class ProviderViewSet(ModelViewSet):
"""Provider Viewset""" """Provider Viewset"""
queryset = Provider.objects.all() queryset = Provider.objects.all()
serializer_class = ProviderSerializer serializer_class = ProviderSerializer
filterset_fields = {
"application": ["isnull"],
}
def get_queryset(self): def get_queryset(self):
return Provider.objects.select_subclasses() return Provider.objects.select_subclasses()

View File

@ -14,6 +14,7 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from guardian.mixins import GuardianUserMixin from guardian.mixins import GuardianUserMixin
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from rest_framework.serializers import Serializer
from structlog import get_logger from structlog import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException from authentik.core.exceptions import PropertyMappingExpressionException
@ -127,7 +128,7 @@ class User(GuardianUserMixin, AbstractUser):
verbose_name_plural = _("Users") verbose_name_plural = _("Users")
class Provider(models.Model): class Provider(SerializerModel):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
name = models.TextField() name = models.TextField()
@ -156,6 +157,11 @@ class Provider(models.Model):
"""Return Form class used to edit this object""" """Return Form class used to edit this object"""
raise NotImplementedError raise NotImplementedError
@property
def serializer(self) -> Type[Serializer]:
"""Get serializer for this model"""
raise NotImplementedError
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1,7 +1,14 @@
"""Flow API Views""" """Flow API Views"""
from django.core.cache import cache from django.core.cache import cache
from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.mixins import ListModelMixin
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
ModelSerializer,
Serializer,
SerializerMethodField,
)
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.flows.models import Flow, FlowStageBinding, Stage from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.flows.planner import cache_key from authentik.flows.planner import cache_key
@ -98,3 +105,14 @@ class FlowStageBindingViewSet(ModelViewSet):
queryset = FlowStageBinding.objects.all() queryset = FlowStageBinding.objects.all()
serializer_class = FlowStageBindingSerializer serializer_class = FlowStageBindingSerializer
filterset_fields = "__all__" filterset_fields = "__all__"
class FlowCacheViewSet(ListModelMixin, GenericViewSet):
"""Info about cached flows"""
queryset = Flow.objects.none()
serializer_class = Serializer
def list(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"pagination": {"count": len(cache.keys("flow_*"))}})

View File

@ -8,7 +8,7 @@ from django.test.client import RequestFactory
from django.utils.encoding import force_str from django.utils.encoding import force_str
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan, FlowPlanner from authentik.flows.planner import FlowPlan, FlowPlanner
@ -40,6 +40,10 @@ class TestFlowExecutor(TestCase):
def setUp(self): def setUp(self):
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
@patch(
"authentik.flows.views.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_existing_plan_diff_flow(self): def test_existing_plan_diff_flow(self):
"""Check that a plan for a different flow cancels the current plan""" """Check that a plan for a different flow cancels the current plan"""
flow = Flow.objects.create( flow = Flow.objects.create(
@ -62,7 +66,7 @@ class TestFlowExecutor(TestCase):
"authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug} "authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}
), ),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 302)
self.assertEqual(cancel_mock.call_count, 2) self.assertEqual(cancel_mock.call_count, 2)
@patch( @patch(
@ -105,10 +109,13 @@ class TestFlowExecutor(TestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}), reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 302)
self.assertIsInstance(response, AccessDeniedResponse) self.assertEqual(response.url, reverse("authentik_core:shell"))
self.assertInHTML(EmptyFlowException.__doc__, response.rendered_content)
@patch(
"authentik.flows.views.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_invalid_flow_redirect(self): def test_invalid_flow_redirect(self):
"""Tests that an invalid flow still redirects""" """Tests that an invalid flow still redirects"""
flow = Flow.objects.create( flow = Flow.objects.create(
@ -121,11 +128,8 @@ class TestFlowExecutor(TestCase):
dest = "/unique-string" dest = "/unique-string"
url = reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}) url = reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(url + f"?{NEXT_ARG_NAME}={dest}") response = self.client.get(url + f"?{NEXT_ARG_NAME}={dest}")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 302)
self.assertJSONEqual( self.assertEqual(response.url, reverse("authentik_core:shell"))
force_str(response.content),
{"type": "redirect", "to": dest},
)
def test_multi_stage_flow(self): def test_multi_stage_flow(self):
"""Test a full flow with multiple stages""" """Test a full flow with multiple stages"""
@ -161,6 +165,10 @@ class TestFlowExecutor(TestCase):
plan: FlowPlan = session[SESSION_KEY_PLAN] plan: FlowPlan = session[SESSION_KEY_PLAN]
self.assertEqual(len(plan.stages), 1) self.assertEqual(len(plan.stages), 1)
@patch(
"authentik.flows.views.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_reevaluate_remove_last(self): def test_reevaluate_remove_last(self):
"""Test planner with re-evaluate (last stage is removed)""" """Test planner with re-evaluate (last stage is removed)"""
flow = Flow.objects.create( flow = Flow.objects.create(

View File

@ -83,7 +83,9 @@ class FlowExecutorView(View):
return to_stage_response(self.request, self.handle_invalid_flow(exc)) return to_stage_response(self.request, self.handle_invalid_flow(exc))
except EmptyFlowException as exc: except EmptyFlowException as exc:
LOGGER.warning("f(exec): Flow is empty", exc=exc) LOGGER.warning("f(exec): Flow is empty", exc=exc)
return to_stage_response(self.request, self.handle_invalid_flow(exc)) # To match behaviour with loading an empty flow plan from cache,
# we don't show an error message here, but rather call _flow_done()
return self._flow_done()
# We don't save the Plan after getting the next stage # We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet # as it hasn't been successfully passed yet
next_stage = self.plan.next(self.request) next_stage = self.plan.next(self.request)
@ -147,7 +149,7 @@ class FlowExecutorView(View):
NEXT_ARG_NAME, "authentik_core:shell" NEXT_ARG_NAME, "authentik_core:shell"
) )
self.cancel() self.cancel()
return redirect_with_qs(next_param) return to_stage_response(self.request, redirect_with_qs(next_param))
def stage_ok(self) -> HttpResponse: def stage_ok(self) -> HttpResponse:
"""Callback called by stages upon successful completion. """Callback called by stages upon successful completion.

View File

@ -113,17 +113,24 @@ class OutpostServiceConnection(models.Model):
objects = InheritanceManager() objects = InheritanceManager()
@property
def state_key(self) -> str:
"""Key used to save connection state in cache"""
return f"outpost_service_connection_{self.pk.hex}"
@property @property
def state(self) -> OutpostServiceConnectionState: def state(self) -> OutpostServiceConnectionState:
"""Get state of service connection""" """Get state of service connection"""
state_key = f"outpost_service_connection_{self.pk.hex}" from authentik.outposts.tasks import outpost_service_connection_state
state = cache.get(state_key, None)
state = cache.get(self.state_key, None)
if not state: if not state:
state = self._get_state() outpost_service_connection_state.delay(self.pk)
cache.set(state_key, state, timeout=0) return OutpostServiceConnectionState("", False)
return state return state
def _get_state(self) -> OutpostServiceConnectionState: def fetch_state(self) -> OutpostServiceConnectionState:
"""Fetch current Service Connection state"""
raise NotImplementedError raise NotImplementedError
@property @property
@ -203,7 +210,7 @@ class DockerServiceConnection(OutpostServiceConnection):
raise ServiceConnectionInvalid from exc raise ServiceConnectionInvalid from exc
return client return client
def _get_state(self) -> OutpostServiceConnectionState: def fetch_state(self) -> OutpostServiceConnectionState:
try: try:
client = self.client() client = self.client()
return OutpostServiceConnectionState( return OutpostServiceConnectionState(
@ -239,7 +246,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
def __str__(self) -> str: def __str__(self) -> str:
return f"Kubernetes Service-Connection {self.name}" return f"Kubernetes Service-Connection {self.name}"
def _get_state(self) -> OutpostServiceConnectionState: def fetch_state(self) -> OutpostServiceConnectionState:
try: try:
client = self.client() client = self.client()
api_instance = VersionApi(client) api_instance = VersionApi(client)
@ -247,7 +254,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
return OutpostServiceConnectionState( return OutpostServiceConnectionState(
version=version.git_version, healthy=True version=version.git_version, healthy=True
) )
except (OpenApiException, HTTPError): except (OpenApiException, HTTPError, ServiceConnectionInvalid):
return OutpostServiceConnectionState(version="", healthy=False) return OutpostServiceConnectionState(version="", healthy=False)
def client(self) -> ApiClient: def client(self) -> ApiClient:

View File

@ -35,21 +35,22 @@ def outpost_controller_all():
@CELERY_APP.task() @CELERY_APP.task()
def outpost_service_connection_state(state_pk: Any): def outpost_service_connection_state(connection_pk: Any):
"""Update cached state of a service connection""" """Update cached state of a service connection"""
connection: OutpostServiceConnection = ( connection: OutpostServiceConnection = (
OutpostServiceConnection.objects.filter(pk=state_pk).select_subclasses().first() OutpostServiceConnection.objects.filter(pk=connection_pk)
.select_subclasses()
.first()
) )
cache.delete(f"outpost_service_connection_{connection.pk.hex}") state = connection.fetch_state()
_ = connection.state cache.set(connection.state_key, state, timeout=None)
@CELERY_APP.task(bind=True, base=MonitoredTask) @CELERY_APP.task(bind=True, base=MonitoredTask)
def outpost_service_connection_monitor(self: MonitoredTask): def outpost_service_connection_monitor(self: MonitoredTask):
"""Regularly check the state of Outpost Service Connections""" """Regularly check the state of Outpost Service Connections"""
for connection in OutpostServiceConnection.objects.select_subclasses(): for connection in OutpostServiceConnection.objects.all():
cache.delete(f"outpost_service_connection_{connection.pk.hex}") outpost_service_connection_state.delay(connection.pk)
_ = connection.state
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL)) self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))

View File

@ -1,11 +1,16 @@
"""policy API Views""" """policy API Views"""
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ( from rest_framework.serializers import (
ModelSerializer, ModelSerializer,
PrimaryKeyRelatedField, PrimaryKeyRelatedField,
Serializer,
SerializerMethodField, SerializerMethodField,
) )
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.policies.forms import GENERAL_FIELDS from authentik.policies.forms import GENERAL_FIELDS
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
@ -68,6 +73,10 @@ class PolicyViewSet(ReadOnlyModelViewSet):
queryset = Policy.objects.all() queryset = Policy.objects.all()
serializer_class = PolicySerializer serializer_class = PolicySerializer
filterset_fields = {
"bindings": ["isnull"],
"promptstage": ["isnull"],
}
def get_queryset(self): def get_queryset(self):
return Policy.objects.select_subclasses() return Policy.objects.select_subclasses()
@ -98,3 +107,14 @@ class PolicyBindingViewSet(ModelViewSet):
serializer_class = PolicyBindingSerializer serializer_class = PolicyBindingSerializer
filterset_fields = ["policy", "target", "enabled", "order", "timeout"] filterset_fields = ["policy", "target", "enabled", "order", "timeout"]
search_fields = ["policy__name"] search_fields = ["policy__name"]
class PolicyCacheViewSet(ListModelMixin, GenericViewSet):
"""Info about cached policies"""
queryset = Policy.objects.none()
serializer_class = Serializer
def list(self, request: Request) -> Response:
"""Info about cached policies"""
return Response(data={"pagination": {"count": len(cache.keys("policy_*"))}})

View File

@ -18,6 +18,7 @@ from django.utils import dateformat, timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key
from jwkest.jws import JWS from jwkest.jws import JWS
from rest_framework.serializers import Serializer
from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
@ -263,6 +264,12 @@ class OAuth2Provider(Provider):
launch_url = urlparse(main_url) launch_url = urlparse(main_url)
return main_url.replace(launch_url.path, "") return main_url.replace(launch_url.path, "")
@property
def serializer(self) -> Type[Serializer]:
from authentik.providers.oauth2.api import OAuth2ProviderSerializer
return OAuth2ProviderSerializer
@property @property
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from authentik.providers.oauth2.forms import OAuth2ProviderForm from authentik.providers.oauth2.forms import OAuth2ProviderForm

View File

@ -8,6 +8,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import Serializer
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import DomainlessURLValidator from authentik.lib.models import DomainlessURLValidator
@ -108,6 +109,12 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
return ProxyProviderForm return ProxyProviderForm
@property
def serializer(self) -> Type[Serializer]:
from authentik.providers.proxy.api import ProxyProviderSerializer
return ProxyProviderSerializer
@property @property
def launch_url(self) -> Optional[str]: def launch_url(self) -> Optional[str]:
"""Use external_host as launch URL""" """Use external_host as launch URL"""

View File

@ -7,6 +7,7 @@ from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from structlog import get_logger from structlog import get_logger
from authentik.core.models import PropertyMapping, Provider from authentik.core.models import PropertyMapping, Provider
@ -145,6 +146,12 @@ class SAMLProvider(Provider):
launch_url = urlparse(self.acs_url) launch_url = urlparse(self.acs_url)
return self.acs_url.replace(launch_url.path, "") return self.acs_url.replace(launch_url.path, "")
@property
def serializer(self) -> Type[Serializer]:
from authentik.providers.saml.api import SAMLPropertyMappingSerializer
return SAMLPropertyMappingSerializer
@property @property
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from authentik.providers.saml.forms import SAMLProviderForm from authentik.providers.saml.forms import SAMLProviderForm

View File

@ -7,13 +7,16 @@ class MessageConsumer(JsonWebsocketConsumer):
"""Consumer which sends django.contrib.messages Messages over WS. """Consumer which sends django.contrib.messages Messages over WS.
channel_name is saved into cache with user_id, and when a add_message is called""" channel_name is saved into cache with user_id, and when a add_message is called"""
session_key: str
def connect(self): def connect(self):
self.accept() self.accept()
cache.set(f"user_{self.scope['user'].pk}_messages_{self.channel_name}", True) self.session_key = self.scope["session"].session_key
cache.set(f"user_{self.session_key}_messages_{self.channel_name}", True, None)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def disconnect(self, close_code): def disconnect(self, close_code):
cache.delete(f"user_{self.scope['user'].pk}_messages_{self.channel_name}") cache.delete(f"user_{self.session_key}_messages_{self.channel_name}")
def event_update(self, event: dict): def event_update(self, event: dict):
"""Event handler which is called by Messages Storage backend""" """Event handler which is called by Messages Storage backend"""

View File

@ -16,7 +16,7 @@ class ChannelsStorage(FallbackStorage):
self.channel = get_channel_layer() self.channel = get_channel_layer()
def _store(self, messages: list[Message], response, *args, **kwargs): def _store(self, messages: list[Message], response, *args, **kwargs):
prefix = f"user_{self.request.user.pk}_messages_" prefix = f"user_{self.request.session.session_key}_messages_"
keys = cache.keys(f"{prefix}*") keys = cache.keys(f"{prefix}*")
if len(keys) < 1: if len(keys) < 1:
return super()._store(messages, response, *args, **kwargs) return super()._store(messages, response, *args, **kwargs)

View File

@ -10,8 +10,8 @@ services:
- internal - internal
environment: environment:
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword} - POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
- POSTGRES_USER=authentik - POSTGRES_USER=${PG_USER:-authentik}
- POSTGRES_DB=authentik - POSTGRES_DB=${PG_DB:-authentik}
env_file: env_file:
- .env - .env
redis: redis:
@ -19,11 +19,13 @@ services:
networks: networks:
- internal - internal
server: server:
image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-stable} image: beryju/authentik:${AUTHENTIK_TAG:-0.13.2-stable}
command: server command: server
environment: environment:
AUTHENTIK_REDIS__HOST: redis AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes: volumes:
- ./media:/media - ./media:/media
@ -42,13 +44,15 @@ services:
env_file: env_file:
- .env - .env
worker: worker:
image: beryju/authentik:${AUTHENTIK_TAG:-0.13.0-stable} image: beryju/authentik:${AUTHENTIK_TAG:-0.13.2-stable}
command: worker command: worker
networks: networks:
- internal - internal
environment: environment:
AUTHENTIK_REDIS__HOST: redis AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes: volumes:
- ./backups:/backups - ./backups:/backups
@ -56,7 +60,7 @@ services:
env_file: env_file:
- .env - .env
static: static:
image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.0-stable} image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.2-stable}
networks: networks:
- internal - internal
labels: labels:
@ -68,7 +72,7 @@ services:
traefik.http.services.static-service.loadbalancer.healthcheck.path: / traefik.http.services.static-service.loadbalancer.healthcheck.path: /
traefik.http.services.static-service.loadbalancer.server.port: '80' traefik.http.services.static-service.loadbalancer.server.port: '80'
volumes: volumes:
- ./media:/media - ./media:/usr/share/nginx/html/media
traefik: traefik:
image: traefik:2.3 image: traefik:2.3
command: command:
@ -81,7 +85,6 @@ services:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
ports: ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443" - "0.0.0.0:443:443"
- "127.0.0.1:8080:8080" - "127.0.0.1:8080:8080"
networks: networks:

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: "0.13.0-stable" version: "0.13.2-stable"
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,7 +4,7 @@
|-----------------------------------|-------------------------|-------------| |-----------------------------------|-------------------------|-------------|
| 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 | 0.13.0-stable | Image tag | | image.tag | 0.13.2-stable | 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 |

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: 0.13.0-stable tag: 0.13.2-stable
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
serverReplicas: 1 serverReplicas: 1

View File

@ -59,6 +59,7 @@ func getCommonOptions() *options.Options {
} }
func doGlobalSetup(config map[string]interface{}) { func doGlobalSetup(config map[string]interface{}) {
log.SetFormatter(&log.JSONFormatter{})
switch config[ConfigLogLevel].(string) { switch config[ConfigLogLevel].(string) {
case "debug": case "debug":
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)

View File

@ -1,3 +1,3 @@
package pkg package pkg
const VERSION = "0.13.0-stable" const VERSION = "0.13.2-stable"

View File

@ -22,11 +22,11 @@ paths:
/admin/metrics/: /admin/metrics/:
get: get:
operationId: admin_metrics_list operationId: admin_metrics_list
description: Return single instance of AdministrationMetricsSerializer description: Login Metrics per 1h
parameters: [] parameters: []
responses: responses:
'200': '200':
description: Overview View description: Login Metrics per 1h
schema: schema:
description: '' description: ''
type: array type: array
@ -35,22 +35,6 @@ paths:
tags: tags:
- admin - admin
parameters: [] parameters: []
/admin/overview/:
get:
operationId: admin_overview_list
description: Return single instance of AdministrationOverviewSerializer
parameters: []
responses:
'200':
description: Overview View
schema:
description: ''
type: array
items:
$ref: '#/definitions/AdministrationOverview'
tags:
- admin
parameters: []
/admin/system_tasks/: /admin/system_tasks/:
get: get:
operationId: admin_system_tasks_list operationId: admin_system_tasks_list
@ -82,6 +66,95 @@ paths:
in: path in: path
required: true required: true
type: string type: string
/admin/version/:
get:
operationId: admin_version_list
description: Get running and latest version.
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: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: Get running and latest version.
schema:
description: ''
type: array
items:
$ref: '#/definitions/Version'
tags:
- admin
parameters: []
/admin/workers/:
get:
operationId: admin_workers_list
description: Get currently connected worker count.
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: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
description: ''
type: object
properties: {}
tags:
- admin
parameters: []
/audit/events/: /audit/events/:
get: get:
operationId: audit_events_list operationId: audit_events_list
@ -1062,6 +1135,59 @@ paths:
required: true required: true
type: string type: string
format: uuid format: uuid
/flows/cached/:
get:
operationId: flows_cached_list
description: Info about cached flows
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: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
description: ''
type: object
properties: {}
tags:
- flows
parameters: []
/flows/instances/: /flows/instances/:
get: get:
operationId: flows_instances_list operationId: flows_instances_list
@ -1702,6 +1828,16 @@ paths:
operationId: policies_all_list operationId: policies_all_list
description: Policy Viewset description: Policy Viewset
parameters: parameters:
- name: bindings__isnull
in: query
description: ''
required: false
type: string
- name: promptstage__isnull
in: query
description: ''
required: false
type: string
- name: ordering - name: ordering
in: query in: query
description: Which field to use when ordering the results. description: Which field to use when ordering the results.
@ -1919,6 +2055,59 @@ paths:
required: true required: true
type: string type: string
format: uuid format: uuid
/policies/cached/:
get:
operationId: policies_cached_list
description: Info about cached policies
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: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
description: ''
type: object
properties: {}
tags:
- policies
parameters: []
/policies/dummy/: /policies/dummy/:
get: get:
operationId: policies_dummy_list operationId: policies_dummy_list
@ -3264,6 +3453,11 @@ paths:
operationId: providers_all_list operationId: providers_all_list
description: Provider Viewset description: Provider Viewset
parameters: parameters:
- name: application__isnull
in: query
description: ''
required: false
type: string
- name: ordering - name: ordering
in: query in: query
description: Which field to use when ordering the results. description: Which field to use when ordering the results.
@ -3309,6 +3503,22 @@ paths:
$ref: '#/definitions/Provider' $ref: '#/definitions/Provider'
tags: tags:
- providers - providers
post:
operationId: providers_all_create
description: Provider Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Provider'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/Provider'
tags:
- providers
parameters: [] parameters: []
/providers/all/{id}/: /providers/all/{id}/:
get: get:
@ -3322,6 +3532,47 @@ paths:
$ref: '#/definitions/Provider' $ref: '#/definitions/Provider'
tags: tags:
- providers - providers
put:
operationId: providers_all_update
description: Provider Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Provider'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Provider'
tags:
- providers
patch:
operationId: providers_all_partial_update
description: Provider Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/Provider'
responses:
'200':
description: ''
schema:
$ref: '#/definitions/Provider'
tags:
- providers
delete:
operationId: providers_all_delete
description: Provider Viewset
parameters: []
responses:
'204':
description: ''
tags:
- providers
parameters: parameters:
- name: id - name: id
in: path in: path
@ -6421,7 +6672,7 @@ paths:
format: uuid format: uuid
definitions: definitions:
AdministrationMetrics: AdministrationMetrics:
description: Overview View description: Login Metrics per 1h
type: object type: object
properties: properties:
logins_per_1h: logins_per_1h:
@ -6432,38 +6683,6 @@ definitions:
title: Logins failed per 1h title: Logins failed per 1h
type: string type: string
readOnly: true readOnly: true
AdministrationOverview:
description: Overview View
type: object
properties:
version:
title: Version
type: string
readOnly: true
version_latest:
title: Version latest
type: string
readOnly: true
worker_count:
title: Worker count
type: integer
readOnly: true
providers_without_application:
title: Providers without application
type: integer
readOnly: true
policies_without_binding:
title: Policies without binding
type: integer
readOnly: true
cached_policies:
title: Cached policies
type: integer
readOnly: true
cached_flows:
title: Cached flows
type: integer
readOnly: true
Task: Task:
description: Serialize TaskInfo and TaskResult description: Serialize TaskInfo and TaskResult
required: required:
@ -6494,6 +6713,22 @@ definitions:
type: array type: array
items: items:
type: string type: string
Version:
description: Get running and latest version.
type: object
properties:
version_current:
title: Version current
type: string
readOnly: true
version_latest:
title: Version latest
type: string
readOnly: true
outdated:
title: Outdated
type: boolean
readOnly: true
Event: Event:
description: Event Serializer description: Event Serializer
required: required:
@ -7480,6 +7715,7 @@ definitions:
description: Provider Serializer description: Provider Serializer
required: required:
- name - name
- application
- authorization_flow - authorization_flow
type: object type: object
properties: properties:
@ -7491,6 +7727,9 @@ definitions:
title: Name title: Name
type: string type: string
minLength: 1 minLength: 1
application:
title: Application
type: string
authorization_flow: authorization_flow:
title: Authorization flow title: Authorization flow
description: Flow used when authorizing this provider. description: Flow used when authorizing this provider.

286
web/package-lock.json generated
View File

@ -102,14 +102,14 @@
} }
}, },
"@patternfly/patternfly": { "@patternfly/patternfly": {
"version": "4.65.6", "version": "4.70.2",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.65.6.tgz", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.70.2.tgz",
"integrity": "sha512-dENO2nZbf5SoEH68coW9U+6FpZmdVnFVjztl7rUeWUPSBUuF1eWld5LT03Q6PVoZuWqqbJxFJodyFKwLb+L9vw==" "integrity": "sha512-XKCHnOjx1JThY3s98AJhsApSsGHPvEdlY7r+b18OecqUnmThVGw3nslzYYrwfCGlJ/xQtV5so29SduH2/uhHzA=="
}, },
"@rollup/plugin-typescript": { "@rollup/plugin-typescript": {
"version": "8.0.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.1.0.tgz",
"integrity": "sha512-2L/kKvM5U4VOm+yVMvPIBF3yMZtQUyopf4YIT+KQbqZBZ8Fsdm7X6yeezy92PMyvvHQG1Pa322MVwxPojQvukA==", "integrity": "sha512-pyQlcGQYRsONUDwXK3ckGPHjPzmjlq4sinzr7emW8ZMb2oZjg9WLcdcP8wyHSvBjvHrLzMayyPy079RROqb4vw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^3.1.0", "@rollup/pluginutils": "^3.1.0",
@ -142,13 +142,13 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.1.tgz",
"integrity": "sha512-kRlt1mE2wrYjspnIupNnPxqsUrRuy02SuXhbpP7J6uu8QasoEmJ78hk0hHz4jOZRmuWwfs2zIXD4tLGgWOKq8A==", "integrity": "sha512-cVlXoQBJ64eNNkQuOB+bS6sK5KWV+Fw+ZYxT+XqjpeXkOPaxh8aeoi9CHz2DsFfbLV91P4AnXZEUdDl+7ktQNg==",
"requires": { "requires": {
"@sentry/core": "5.29.0", "@sentry/core": "5.29.1",
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"@sentry/utils": "5.29.0", "@sentry/utils": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -160,14 +160,14 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.1.tgz",
"integrity": "sha512-a1sZBJ2u3NG0YDlGvOTwUCWiNjhfmDtAQiKK1o6RIIbcrWy9TlSps7CYDkBP239Y3A4pnvohjEEKEP3v3L3LZQ==", "integrity": "sha512-SMybIx9IlswkJ7a61ez/zjdiMdAo51Adpo4nVrzke2k84U/t726/EbJj0FJ4vVgsGdLCvSSZ6v7BQlINcwWupg==",
"requires": { "requires": {
"@sentry/hub": "5.29.0", "@sentry/hub": "5.29.1",
"@sentry/minimal": "5.29.0", "@sentry/minimal": "5.29.1",
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"@sentry/utils": "5.29.0", "@sentry/utils": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -179,12 +179,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.1.tgz",
"integrity": "sha512-kcDPQsRG4cFdmqDh+TzjeO7lWYxU8s1dZYAbbl1J4uGKmhNB0J7I4ak4SGwTsXLY6fhbierxr6PRaoNojCxjPw==", "integrity": "sha512-Ig/vqCiJcsnGaWajkWRFH+5IKeo50ZtsjM0zJb8IfTadLjQuF/gTQst0aXO3l6q4HzveeGsELY8jlm6WVcq9Aw==",
"requires": { "requires": {
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"@sentry/utils": "5.29.0", "@sentry/utils": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -196,12 +196,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.1.tgz",
"integrity": "sha512-nhXofdjtO41/caiF1wk1oT3p/QuhOZDYdF/b29DoD2MiAMK9IjhhOXI/gqaRpDKkXlDvd95fDTcx4t/MqqcKXA==", "integrity": "sha512-lAa3+Duxum1qQvR0tKiBUsH6Ehit3g/vO53SqBib7YK3qdvIUWHacmkJvfz/AeSvVnpJ9bsBMCVRJNSVe8BPVA==",
"requires": { "requires": {
"@sentry/hub": "5.29.0", "@sentry/hub": "5.29.1",
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -213,51 +213,17 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.29.1.tgz",
"integrity": "sha512-2ZITUH7Eur7IkmRAd5gw8Xt2Sfc28btCnT7o2P2J8ZPD65e99ATqjxXPokx0+6zEkTsstIDD3mbyuwkpbuvuTA==", "integrity": "sha512-iWfPtDhf5X7N9R5WB3vX/wlyFVsGG8iMx4hLIP+6bj8EcPYnZfeP6Sxn65a0ACT/FKv7SMBoZ1qPDzmvk0bviw==",
"requires": { "requires": {
"@sentry/hub": "5.29.0", "@sentry/hub": "5.29.1",
"@sentry/minimal": "5.29.0", "@sentry/minimal": "5.29.1",
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"@sentry/utils": "5.29.0", "@sentry/utils": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
"@sentry/hub": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.0.tgz",
"integrity": "sha512-kcDPQsRG4cFdmqDh+TzjeO7lWYxU8s1dZYAbbl1J4uGKmhNB0J7I4ak4SGwTsXLY6fhbierxr6PRaoNojCxjPw==",
"requires": {
"@sentry/types": "5.29.0",
"@sentry/utils": "5.29.0",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.0.tgz",
"integrity": "sha512-nhXofdjtO41/caiF1wk1oT3p/QuhOZDYdF/b29DoD2MiAMK9IjhhOXI/gqaRpDKkXlDvd95fDTcx4t/MqqcKXA==",
"requires": {
"@sentry/hub": "5.29.0",
"@sentry/types": "5.29.0",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.0.tgz",
"integrity": "sha512-iDkxT/9sT3UF+Xb+JyLjZ5caMXsgLfRyV9VXQEiR2J6mgpMielj184d9jeF3bm/VMuAf/VFFqrHlcVsVgmrrMw=="
},
"@sentry/utils": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.0.tgz",
"integrity": "sha512-b2B1gshw2u3EHlAi84PuI5sfmLKXW1z9enMMhNuuNT/CoRp+g5kMAcUv/qYTws7UNnYSvTuVGuZG30v1e0hP9A==",
"requires": {
"@sentry/types": "5.29.0",
"tslib": "^1.9.3"
}
},
"tslib": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -266,16 +232,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz",
"integrity": "sha512-iDkxT/9sT3UF+Xb+JyLjZ5caMXsgLfRyV9VXQEiR2J6mgpMielj184d9jeF3bm/VMuAf/VFFqrHlcVsVgmrrMw==" "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "5.29.0", "version": "5.29.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.1.tgz",
"integrity": "sha512-b2B1gshw2u3EHlAi84PuI5sfmLKXW1z9enMMhNuuNT/CoRp+g5kMAcUv/qYTws7UNnYSvTuVGuZG30v1e0hP9A==", "integrity": "sha512-FOhWxASvIQREAlSuWf3Vmb4uIkG0fmRdHkULpuv5dFmrMX2PpudYAppQtS8K9V4BYxFy6KFdUht1Qz5zYTecMw==",
"requires": { "requires": {
"@sentry/types": "5.29.0", "@sentry/types": "5.29.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -287,9 +253,9 @@
} }
}, },
"@types/chart.js": { "@types/chart.js": {
"version": "2.9.28", "version": "2.9.29",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.28.tgz", "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.29.tgz",
"integrity": "sha512-9YYhsxRngRJb0dkuaU5BezkF+zvvVHnwdRw+rtlahtFb4zqNf9YSgWsOq+dLYeh0fqsWmHUYLR64eNigh02F+w==", "integrity": "sha512-WOZMitUU3gHDM0oQsCsVivX+oDsIki93szcTmmUPBm39cCvAELBjokjSDVOoA3xiIEbb+jp17z/3S2tIqruwOQ==",
"requires": { "requires": {
"moment": "^2.10.2" "moment": "^2.10.2"
} }
@ -393,156 +359,70 @@
} }
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz",
"integrity": "sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==", "integrity": "sha512-h6/V46o6aXpKRlarP1AiJEXuCJ7cMQdlpfMDrcllIgX3dFkLwEBTXAoNP98ZoOmqd1xvymMVRAI4e7yVvlzWEg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.9.1", "@typescript-eslint/experimental-utils": "4.10.0",
"@typescript-eslint/scope-manager": "4.9.1", "@typescript-eslint/scope-manager": "4.10.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0", "regexpp": "^3.0.0",
"semver": "^7.3.2", "semver": "^7.3.2",
"tsutils": "^3.17.1" "tsutils": "^3.17.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz",
"integrity": "sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.9.1",
"@typescript-eslint/visitor-keys": "4.9.1"
}
},
"@typescript-eslint/types": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.1.tgz",
"integrity": "sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==",
"dev": true
},
"@typescript-eslint/visitor-keys": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz",
"integrity": "sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.9.1",
"eslint-visitor-keys": "^2.0.0"
}
}
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.10.0.tgz",
"integrity": "sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==", "integrity": "sha512-opX+7ai1sdWBOIoBgpVJrH5e89ra1KoLrJTz0UtWAa4IekkKmqDosk5r6xqRaNJfCXEfteW4HXQAwMdx+jjEmw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.9.1", "@typescript-eslint/scope-manager": "4.10.0",
"@typescript-eslint/types": "4.9.1", "@typescript-eslint/types": "4.10.0",
"@typescript-eslint/typescript-estree": "4.9.1", "@typescript-eslint/typescript-estree": "4.10.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.0.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz",
"integrity": "sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.9.1",
"@typescript-eslint/visitor-keys": "4.9.1"
}
},
"@typescript-eslint/types": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.1.tgz",
"integrity": "sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz",
"integrity": "sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.9.1",
"@typescript-eslint/visitor-keys": "4.9.1",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz",
"integrity": "sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.9.1",
"eslint-visitor-keys": "^2.0.0"
}
},
"globby": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
"integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
"dev": true,
"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/parser": { "@typescript-eslint/parser": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.10.0.tgz",
"integrity": "sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==", "integrity": "sha512-amBvUUGBMadzCW6c/qaZmfr3t9PyevcSWw7hY2FuevdZVp5QPw/K76VSQ5Sw3BxlgYCHZcK6DjIhSZK0PQNsQg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.9.1", "@typescript-eslint/scope-manager": "4.10.0",
"@typescript-eslint/types": "4.9.1", "@typescript-eslint/types": "4.10.0",
"@typescript-eslint/typescript-estree": "4.9.1", "@typescript-eslint/typescript-estree": "4.10.0",
"debug": "^4.1.1" "debug": "^4.1.1"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz",
"integrity": "sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==", "integrity": "sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.9.1", "@typescript-eslint/types": "4.10.0",
"@typescript-eslint/visitor-keys": "4.9.1" "@typescript-eslint/visitor-keys": "4.10.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.10.0.tgz",
"integrity": "sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==", "integrity": "sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==",
"dev": true "dev": true
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz",
"integrity": "sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==", "integrity": "sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.9.1", "@typescript-eslint/types": "4.10.0",
"@typescript-eslint/visitor-keys": "4.9.1", "@typescript-eslint/visitor-keys": "4.10.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",
@ -568,12 +448,12 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.9.1", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz",
"integrity": "sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==", "integrity": "sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.9.1", "@typescript-eslint/types": "4.10.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
@ -2680,9 +2560,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.34.2", "version": "2.35.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.34.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.35.1.tgz",
"integrity": "sha512-mvtQLqu3cNeoctS+kZ09iOPxrc1P1/Bt1z15enuQ5feyKOdM3MJAVFjjsygurDpSWn530xB4AlA83TWIzRstXA==", "integrity": "sha512-q5KxEyWpprAIcainhVy6HfRttD9kutQpHbeqDTWnqAFNJotiojetK6uqmcydNMymBEtC4I8bCYR+J3mTMqeaUA==",
"requires": { "requires": {
"fsevents": "~2.1.2" "fsevents": "~2.1.2"
} }

View File

@ -8,26 +8,26 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^5.15.1",
"@patternfly/patternfly": "^4.65.6", "@patternfly/patternfly": "^4.70.2",
"@sentry/browser": "^5.29.0", "@sentry/browser": "^5.29.1",
"@sentry/tracing": "^5.29.0", "@sentry/tracing": "^5.29.1",
"@types/chart.js": "^2.9.28", "@types/chart.js": "^2.9.29",
"@types/codemirror": "0.0.102", "@types/codemirror": "0.0.102",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"codemirror": "^5.58.3", "codemirror": "^5.58.3",
"construct-style-sheets-polyfill": "^2.4.3", "construct-style-sheets-polyfill": "^2.4.3",
"lit-element": "^2.4.0", "lit-element": "^2.4.0",
"lit-html": "^1.3.0", "lit-html": "^1.3.0",
"rollup": "^2.34.2", "rollup": "^2.35.1",
"rollup-plugin-copy": "^3.3.0", "rollup-plugin-copy": "^3.3.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
"rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-external-globals": "^0.6.1",
"tslib": "^2.0.3" "tslib": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^8.0.0", "@rollup/plugin-typescript": "^8.1.0",
"@typescript-eslint/eslint-plugin": "^4.9.1", "@typescript-eslint/eslint-plugin": "^4.10.0",
"@typescript-eslint/parser": "^4.9.1", "@typescript-eslint/parser": "^4.10.0",
"eslint": "^7.15.0", "eslint": "^7.15.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",

View File

@ -1,4 +1,4 @@
import { DefaultClient, PBResponse, QueryArguments } from "./client"; import { DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Application { export class Application {
pk: string; pk: string;

View File

@ -1,4 +1,4 @@
import { NotFoundError, RequestError } from "./errors"; import { NotFoundError, RequestError } from "./Error";
export const VERSION = "v2beta"; export const VERSION = "v2beta";

View File

@ -1,4 +1,4 @@
import { DefaultClient } from "./client"; import { DefaultClient } from "./Client";
import * as Sentry from "@sentry/browser"; import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing"; import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants"; import { VERSION } from "../constants";

View File

@ -1,4 +1,4 @@
import { DefaultClient } from "./client"; import { DefaultClient } from "./Client";
export class AuditEvent { export class AuditEvent {
//audit/events/top_per_user/?filter_action=authorize_application //audit/events/top_per_user/?filter_action=authorize_application

View File

@ -1,4 +1,4 @@
import { DefaultClient, PBResponse, QueryArguments } from "./client"; import { DefaultClient, PBResponse, QueryArguments } from "./Client";
export enum FlowDesignation { export enum FlowDesignation {
Authentication = "authentication", Authentication = "authentication",
@ -33,6 +33,12 @@ export class Flow {
static list(filter?: QueryArguments): Promise<PBResponse<Flow>> { static list(filter?: QueryArguments): Promise<PBResponse<Flow>> {
return DefaultClient.fetch<PBResponse<Flow>>(["flows", "instances"], filter); return DefaultClient.fetch<PBResponse<Flow>>(["flows", "instances"], filter);
} }
static cached(): Promise<number> {
return DefaultClient.fetch<PBResponse<Flow>>(["flows", "cached"]).then(r => {
return r.pagination.count;
});
}
} }
export class Stage { export class Stage {

24
web/src/api/Policies.ts Normal file
View File

@ -0,0 +1,24 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Policy {
pk: string;
name: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
}
static list(filter?: QueryArguments): Promise<PBResponse<Policy>> {
return DefaultClient.fetch<PBResponse<Policy>>(["policies", "all"], filter);
}
static cached(): Promise<number> {
return DefaultClient.fetch<PBResponse<Policy>>(["policies", "cached"]).then(r => {
return r.pagination.count;
});
}
}

View File

@ -1,10 +1,5 @@
import { DefaultClient, PBResponse, QueryArguments } from "./client"; import { DefaultClient, PBResponse, QueryArguments } from "./Client";
import { Policy } from "./Policies";
export interface Policy {
pk: string;
name: string;
[key: string]: unknown;
}
export class PolicyBinding { export class PolicyBinding {
pk: string; pk: string;

19
web/src/api/Providers.ts Normal file
View File

@ -0,0 +1,19 @@
import { DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Provider {
pk: number;
name: string;
authorization_flow: string;
constructor() {
throw Error();
}
static get(slug: string): Promise<Provider> {
return DefaultClient.fetch<Provider>(["providers", "all", slug]);
}
static list(filter?: QueryArguments): Promise<PBResponse<Provider>> {
return DefaultClient.fetch<PBResponse<Provider>>(["providers", "all"], filter);
}
}

View File

@ -1,4 +1,4 @@
import { DefaultClient, PBResponse, QueryArguments } from "./client"; import { DefaultClient, PBResponse, QueryArguments } from "./Client";
export class Source { export class Source {
pk: string; pk: string;

View File

@ -1,4 +1,4 @@
import { DefaultClient } from "./client"; import { DefaultClient } from "./Client";
interface TokenResponse { interface TokenResponse {
key: string; key: string;

View File

@ -1,4 +1,4 @@
import { DefaultClient, PBResponse } from "./client"; import { DefaultClient, PBResponse } from "./Client";
let _globalMePromise: Promise<User>; let _globalMePromise: Promise<User>;

17
web/src/api/Versions.ts Normal file
View File

@ -0,0 +1,17 @@
import { DefaultClient } from "./Client";
export class Version {
version_current: string;
version_latest: string;
outdated: boolean;
constructor() {
throw Error();
}
static get(): Promise<Version> {
return DefaultClient.fetch<Version>(["admin", "version"]);
}
}

View File

@ -1,20 +0,0 @@
import { DefaultClient } from "./client";
export class AdminOverview {
version: string;
version_latest: string;
worker_count: number;
providers_without_application: number;
policies_without_binding: number;
cached_policies: number;
cached_flows: number;
constructor() {
throw Error();
}
static get(): Promise<AdminOverview> {
return DefaultClient.fetch<AdminOverview>(["admin", "overview"]);
}
}

View File

@ -162,10 +162,12 @@ select[multiple] {
background-color: var(--ak-dark-background-light); background-color: var(--ak-dark-background-light);
color: var(--ak-dark-foreground); color: var(--ak-dark-foreground);
} }
.pf-m-tertiary,
.pf-c-button.pf-m-tertiary { .pf-c-button.pf-m-tertiary {
--pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker); --pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker);
color: var(--ak-dark-foreground-darker); color: var(--ak-dark-foreground-darker);
} }
.pf-m-tertiary:hover,
.pf-c-button.pf-m-tertiary:hover { .pf-c-button.pf-m-tertiary:hover {
--pf-c-button--after--BorderColor: var(--ak-dark-background-lighter); --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter);
} }
@ -197,6 +199,9 @@ select[multiple] {
.pf-c-login__main { .pf-c-login__main {
background-color: var(--ak-dark-background); background-color: var(--ak-dark-background);
} }
.pf-c-login__main-body {
color: var(--ak-dark-foreground);
}
.pf-c-login__main-footer-links-item-link > img { .pf-c-login__main-footer-links-item-link > img {
filter: invert(1); filter: invert(1);
} }

View File

@ -28,4 +28,4 @@ export const ColorStyles = css`
background-color: var(--pf-global--danger-color--100); background-color: var(--pf-global--danger-color--100);
} }
`; `;
export const VERSION = "0.13.0-stable"; export const VERSION = "0.13.2-stable";

View File

@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
// @ts-ignore // @ts-ignore
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css"; import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
import { tokenByIdentifier } from "../../api/token"; import { tokenByIdentifier } from "../../api/Tokens";
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants"; import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
@customElement("ak-token-copy-button") @customElement("ak-token-copy-button")

View File

@ -2,6 +2,7 @@ import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { ColorStyles } from "../../constants";
@customElement("ak-aggregate-card") @customElement("ak-aggregate-card")
export class AggregateCard extends LitElement { export class AggregateCard extends LitElement {
@ -24,22 +25,26 @@ export class AggregateCard extends LitElement {
text-align: center; text-align: center;
color: var(--pf-global--Color--100); color: var(--pf-global--Color--100);
} }
`]); `, ColorStyles]);
} }
renderInner(): TemplateResult { renderInner(): TemplateResult {
return html`<slot></slot>`; return html`<slot></slot>`;
} }
renderHeaderLink(): TemplateResult {
return html`${this.headerLink ? html`<a href="${this.headerLink}">
<i class="fa fa-external-link-alt"> </i>
</a>` : ""}`;
}
render(): TemplateResult { render(): TemplateResult {
return html`<div class="pf-c-card pf-c-card-aggregate"> return html`<div class="pf-c-card pf-c-card-aggregate">
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
<div class="pf-c-card__header-main"> <div class="pf-c-card__header-main">
<i class="${ifDefined(this.icon)}"></i> ${this.header ? gettext(this.header) : ""} <i class="${ifDefined(this.icon)}"></i> ${this.header ? gettext(this.header) : ""}
</div> </div>
${this.headerLink ? html`<a href="${this.headerLink}"> ${this.renderHeaderLink()}
<i class="fa fa-external-link-alt"> </i>
</a>` : ""}
</div> </div>
<div class="pf-c-card__body center-value"> <div class="pf-c-card__body center-value">
${this.renderInner()} ${this.renderInner()}

View File

@ -1,6 +1,6 @@
import { gettext } from "django"; import { gettext } from "django";
import { LitElement, html, customElement, TemplateResult, property } from "lit-element"; import { LitElement, html, customElement, TemplateResult, property } from "lit-element";
import { DefaultClient } from "../../api/client"; import { DefaultClient } from "../../api/Client";
import "./Message"; import "./Message";
import { APIMessage } from "./Message"; import { APIMessage } from "./Message";

View File

@ -1,8 +1,8 @@
import { gettext } from "django"; import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element"; import { customElement, html, property, TemplateResult } from "lit-element";
import { PBResponse } from "../../api/client"; import { PBResponse } from "../../api/Client";
import { PolicyBinding } from "../../api/policy_binding";
import { Table } from "../../elements/table/Table"; import { Table } from "../../elements/table/Table";
import { PolicyBinding } from "../../api/PolicyBindings";
import "../../elements/Tabs"; import "../../elements/Tabs";
import "../../elements/AdminLoginsChart"; import "../../elements/AdminLoginsChart";

View File

@ -65,6 +65,32 @@ export class SidebarItem {
} }
}); });
} }
async render(activePath: string): Promise<TemplateResult> {
if (this.condition) {
const result = await this.condition();
if (!result) {
return html``;
}
}
return html` <li class="pf-c-nav__item ${this.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}">
${this.path ?
html`<a href="#${this.path}" class="pf-c-nav__link ${this.isActive(activePath) ? "pf-m-current" : ""}">
${this.name}
</a>` :
html`<a class="pf-c-nav__link" aria-expanded="true">
${this.name}
<span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</a>
<section class="pf-c-nav__subnav">
<ul class="pf-c-nav__simple-list">
${this._children.map((i) => until(i.render(activePath), html``))}
</ul>
</section>`}
</li>`;
}
} }
@customElement("ak-sidebar") @customElement("ak-sidebar")
@ -118,37 +144,11 @@ export class Sidebar extends LitElement {
}); });
} }
async renderItem(item: SidebarItem): Promise<TemplateResult> {
if (item.condition) {
const result = await item.condition();
if (!result) {
return html``;
}
}
return html` <li class="pf-c-nav__item ${item.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}">
${item.path ?
html`<a href="#${item.path}" class="pf-c-nav__link ${item.isActive(this.activePath) ? "pf-m-current": ""}">
${item.name}
</a>` :
html`<a class="pf-c-nav__link" aria-expanded="true">
${item.name}
<span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</a>
<section class="pf-c-nav__subnav">
<ul class="pf-c-nav__simple-list">
${item._children.map((i) => until(this.renderItem(i), html``))}
</ul>
</section>`}
</li>`;
}
render(): TemplateResult { render(): TemplateResult {
return html`<nav class="pf-c-nav" aria-label="Global"> return html`<nav class="pf-c-nav" aria-label="Global">
<ak-sidebar-brand></ak-sidebar-brand> <ak-sidebar-brand></ak-sidebar-brand>
<ul class="pf-c-nav__list"> <ul class="pf-c-nav__list">
${this.items.map((i) => until(this.renderItem(i), html``))} ${this.items.map((i) => until(i.render(this.activePath), html``))}
</ul> </ul>
<ak-sidebar-user></ak-sidebar-user> <ak-sidebar-user></ak-sidebar-user>
</nav>`; </nav>`;

View File

@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import PageStyle from "@patternfly/patternfly/components/Page/page.css"; import PageStyle from "@patternfly/patternfly/components/Page/page.css";
// @ts-ignore // @ts-ignore
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import { Config } from "../../api/config"; import { Config } from "../../api/Config";
export const DefaultConfig: Config = { export const DefaultConfig: Config = {
branding_logo: " /static/dist/assets/icons/icon_left_brand.svg", branding_logo: " /static/dist/assets/icons/icon_left_brand.svg",
@ -33,6 +33,7 @@ export class SidebarBrand extends LitElement {
.pf-c-brand img { .pf-c-brand img {
width: 100%; width: 100%;
padding: 0 .5rem; padding: 0 .5rem;
height: 42px;
} }
`, `,
]; ];

View File

@ -5,7 +5,7 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css";
import fa from "@fortawesome/fontawesome-free/css/all.css"; import fa from "@fortawesome/fontawesome-free/css/all.css";
// @ts-ignore // @ts-ignore
import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css"; import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css";
import { User } from "../../api/user"; import { User } from "../../api/Users";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
@customElement("ak-sidebar-user") @customElement("ak-sidebar-user")

View File

@ -1,6 +1,6 @@
import { gettext } from "django"; import { gettext } from "django";
import { CSSResult, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, html, LitElement, property, TemplateResult } from "lit-element";
import { PBResponse } from "../../api/client"; import { PBResponse } from "../../api/Client";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import "./TablePagination"; import "./TablePagination";

View File

@ -1,6 +1,6 @@
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { PBPagination } from "../../api/client"; import { PBPagination } from "../../api/Client";
@customElement("ak-table-pagination") @customElement("ak-table-pagination")
export class TablePagination extends LitElement { export class TablePagination extends LitElement {

View File

@ -1,17 +1,19 @@
import { customElement } from "lit-element"; import { customElement } from "lit-element";
import { User } from "../api/user"; import { User } from "../api/Users";
import { SidebarItem } from "../elements/sidebar/Sidebar"; import { SidebarItem } from "../elements/sidebar/Sidebar";
import { SLUG_REGEX } from "../elements/router/Route"; import { SLUG_REGEX } from "../elements/router/Route";
import { Interface } from "./Interface"; import { Interface } from "./Interface";
export const SIDEBAR_ITEMS: SidebarItem[] = [ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Library", "/library/"), new SidebarItem("Library", "/library/"),
new SidebarItem("Monitor", "/audit/audit").when((): Promise<boolean> => { new SidebarItem("Monitor").children(
new SidebarItem("Overview", "/administration/overview/"),
new SidebarItem("System Tasks", "/administration/tasks/"),
new SidebarItem("Events", "/audit/audit"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Administration").children( new SidebarItem("Administration").children(
new SidebarItem("Overview", "/administration/overview-ng/"),
new SidebarItem("System Tasks", "/administration/tasks/"),
new SidebarItem("Applications", "/administration/applications/").activeWhen( new SidebarItem("Applications", "/administration/applications/").activeWhen(
`^/applications/(?<slug>${SLUG_REGEX})/$` `^/applications/(?<slug>${SLUG_REGEX})/$`
), ),
@ -19,27 +21,29 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
`^/sources/(?<slug>${SLUG_REGEX})/$`, `^/sources/(?<slug>${SLUG_REGEX})/$`,
), ),
new SidebarItem("Providers", "/administration/providers/"), new SidebarItem("Providers", "/administration/providers/"),
new SidebarItem("Flows").children( new SidebarItem("Outposts", "/administration/outposts/"),
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})/$`), new SidebarItem("Outpost Service Connections", "/administration/outposts/service_connections/"),
new SidebarItem("Stages", "/administration/stages/"),
new SidebarItem("Prompts", "/administration/stages/prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
),
new SidebarItem("User Management").children(
new SidebarItem("User", "/administration/users/"),
new SidebarItem("Groups", "/administration/groups/")
),
new SidebarItem("Outposts").children(
new SidebarItem("Outposts", "/administration/outposts/"),
new SidebarItem("Service Connections", "/administration/outposts/service_connections/")
),
new SidebarItem("Policies", "/administration/policies/"), new SidebarItem("Policies", "/administration/policies/"),
new SidebarItem("Property Mappings", "/administration/property-mappings"), new SidebarItem("Property Mappings", "/administration/property-mappings"),
new SidebarItem("Certificates", "/administration/crypto/certificates"), new SidebarItem("Certificates", "/administration/crypto/certificates"),
new SidebarItem("Tokens", "/administration/tokens/"), new SidebarItem("Tokens", "/administration/tokens/"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}) }),
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})/$`),
new SidebarItem("Stages", "/administration/stages/"),
new SidebarItem("Prompts", "/administration/stages/prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),
new SidebarItem("User Management").children(
new SidebarItem("User", "/administration/users/"),
new SidebarItem("Groups", "/administration/groups/")
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),
]; ];
@customElement("ak-interface-admin") @customElement("ak-interface-admin")

View File

@ -1,8 +1,8 @@
import { gettext } from "django"; import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import { Application } from "../api/application"; import { Application } from "../api/Applications";
import { PBResponse } from "../api/client"; import { PBResponse } from "../api/Client";
import { COMMON_STYLES } from "../common/styles"; import { COMMON_STYLES } from "../common/styles";
import { loading, truncate } from "../utils"; import { loading, truncate } from "../utils";

View File

@ -1,71 +1,27 @@
import { gettext } from "django"; import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
import { AdminOverview } from "../../api/admin_overview"; import { DefaultClient } from "../../api/Client";
import { DefaultClient } from "../../api/client";
import { User } from "../../api/user";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { AggregatePromiseCard } from "../../elements/cards/AggregatePromiseCard";
import { SpinnerSize } from "../../elements/Spinner";
import "../../elements/AdminLoginsChart"; import "../../elements/AdminLoginsChart";
import "../../elements/cards/AggregatePromiseCard";
import "./TopApplicationsTable"; import "./TopApplicationsTable";
import "./cards/AdminStatusCard";
@customElement("ak-admin-status-card") import "./cards/FlowCacheStatusCard";
export class AdminStatusCard extends AggregatePromiseCard { import "./cards/PolicyCacheStatusCard";
import "./cards/PolicyUnboundStatusCard";
@property({type: Number}) import "./cards/ProviderStatusCard";
value?: number; import "./cards/UserCountStatusCard";
import "./cards/VersionStatusCard";
@property() import "./cards/WorkerStatusCard";
warningText?: string;
@property({type: Number})
lessThanThreshold?: number;
renderNone(): TemplateResult {
return html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`;
}
renderGood(): TemplateResult {
return html`<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> ${this.value}
</p>`;
}
renderBad(): TemplateResult {
return html`<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> ${this.value}
</p>
<p class="subtext">${this.warningText ? gettext(this.warningText) : ""}</p>`;
}
renderInner(): TemplateResult {
if (!this.value) {
return this.renderNone();
}
return html``;
}
}
@customElement("ak-admin-overview") @customElement("ak-admin-overview")
export class AdminOverviewPage extends LitElement { export class AdminOverviewPage extends LitElement {
@property({attribute: false})
data?: AdminOverview;
@property({attribute: false})
users?: Promise<number>;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return COMMON_STYLES; return COMMON_STYLES;
} }
firstUpdated(): void {
AdminOverview.get().then(value => this.data = value);
this.users = User.count();
}
render(): TemplateResult { render(): TemplateResult {
return html`<section class="pf-c-page__main-section pf-m-light"> return html`<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content"> <div class="pf-c-content">
@ -80,48 +36,20 @@ export class AdminOverviewPage extends LitElement {
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;"> <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
<ak-top-applications-table></ak-top-applications-table> <ak-top-applications-table></ak-top-applications-table>
</ak-aggregate-card> </ak-aggregate-card>
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers"> <ak-admin-status-card-provider class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/">
${this.data ? </ak-admin-status-card-provider>
this.data?.worker_count < 1 ? <ak-admin-status-card-policy-unbound class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-infrastructure" header="Policies" headerLink="#/administration/policies/">
html`<p class="ak-aggregate-card"> </ak-admin-status-card-policy-unbound>
<i class="fa fa-exclamation-triangle"></i> ${this.data?.worker_count} <ak-admin-status-card-user-count class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-user" header="Users" headerLink="#/administration/users/">
</p> </ak-admin-status-card-user-count>
<p class="subtext">${gettext("No workers connected.")}</p>` : <ak-admin-status-version class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-bundle" header="Version" headerLink="https://github.com/BeryJu/authentik/releases">
html`<p class="ak-aggregate-card"> </ak-admin-status-version>
<i class="fa fa-check-circle"></i> ${this.data?.worker_count} <ak-admin-status-card-workers class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers">
</p>` </ak-admin-status-card-workers>
: html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`} <ak-admin-status-card-policy-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Policies">
</ak-aggregate-card> </ak-admin-status-card-policy-cache>
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/"> <ak-admin-status-card-flow-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Flows">
${this.data ? </ak-admin-status-card-flow-cache>
this.data?.providers_without_application > 1 ?
html`<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> 0
</p>
<p class="subtext">${gettext("At least one Provider has no application assigned.")}</p>` :
html`<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> 0
</p>`
: html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`}
</ak-aggregate-card>
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Policies" headerLink="#/administration/policies/">
${this.data ?
this.data?.policies_without_binding > 1 ?
html`<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> 0
</p>
<p class="subtext">${gettext("Policies without binding exist.")}</p>` :
html`<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> 0
</p>`
: html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`}
</ak-aggregate-card>
<ak-aggregate-card-promise
icon="pf-icon pf-icon-user"
header="Users"
headerLink="#/administration/users/"
.promise=${this.users}>
</ak-aggregate-card-promise>
</div> </div>
</section>`; </section>`;
} }

View File

@ -1,6 +1,6 @@
import { gettext } from "django"; import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { AuditEvent, TopNEvent } from "../../api/events"; import { AuditEvent, TopNEvent } from "../../api/Events";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Spinner"; import "../../elements/Spinner";

View File

@ -0,0 +1,37 @@
import { html, TemplateResult } from "lit-html";
import { until } from "lit-html/directives/until";
import { AggregateCard } from "../../../elements/cards/AggregateCard";
import { SpinnerSize } from "../../../elements/Spinner";
export interface AdminStatus {
icon: string;
message?: string;
}
export abstract class AdminStatusCard<T> extends AggregateCard {
abstract getPrimaryValue(): Promise<T>;
abstract getStatus(value: T): Promise<AdminStatus>;
value?: T;
renderValue(): TemplateResult {
return html`${this.value}`;
}
renderInner(): TemplateResult {
return html`<p class="center-value">
${until(this.getPrimaryValue().then((v) => {
this.value = v;
return this.getStatus(v);
}).then((status) => {
return html`<p class="ak-aggregate-card">
<i class="${status.icon}"></i> ${this.renderValue()}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)}
</p>`;
}
}

View File

@ -0,0 +1,36 @@
import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element";
import { Flow } from "../../../api/Flows";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
@customElement("ak-admin-status-card-flow-cache")
export class FlowCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Flow.cached();
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext("No flows cached."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/flow/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}

View File

@ -0,0 +1,37 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { TemplateResult, html } from "lit-html";
import { Policy } from "../../../api/Policies";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
@customElement("ak-admin-status-card-policy-cache")
export class PolicyCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.cached();
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext("No policies cached. Users may experience slow response times."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/policy/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}

View File

@ -0,0 +1,31 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Policy } from "../../../api/Policies";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-policy-unbound")
export class PolicyUnboundStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.list({
"bindings__isnull": true,
"promptstage__isnull": true,
}).then((response) => {
return response.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext("Policies without binding exist."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}
}

View File

@ -0,0 +1,30 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Provider } from "../../../api/Providers";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-provider")
export class ProviderStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Provider.list({
"application__isnull": true
}).then((response) => {
return response.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext("Warning: At least one Provider has no application assigned."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}
}

View File

@ -0,0 +1,19 @@
import { customElement } from "lit-element";
import { User } from "../../../api/Users";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-user-count")
export class UserCountStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return User.count();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getStatus(value: number): Promise<AdminStatus> {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}

View File

@ -0,0 +1,31 @@
import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element";
import { Version } from "../../../api/Versions";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-version")
export class VersionStatusCard extends AdminStatusCard<Version> {
getPrimaryValue(): Promise<Version> {
return Version.get();
}
getStatus(value: Version): Promise<AdminStatus> {
if (value.outdated) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext(`${value.version_latest} is available!`),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success",
message: gettext("Up-to-date!")
});
}
}
renderValue(): TemplateResult {
return html`${this.value?.version_current}`;
}
}

View File

@ -0,0 +1,28 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { DefaultClient, PBResponse } from "../../../api/Client";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
@customElement("ak-admin-status-card-workers")
export class WorkersStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return DefaultClient.fetch<PBResponse<number>>(["admin", "workers"]).then((r) => {
return r.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: gettext("No workers connected. Background tasks will not run."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success"
});
}
}
}

View File

@ -1,7 +1,7 @@
import { gettext } from "django"; import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element"; import { customElement, html, TemplateResult } from "lit-element";
import { Application } from "../../api/application"; import { Application } from "../../api/Applications";
import { PBResponse } from "../../api/client"; import { PBResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage"; import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";

View File

@ -1,7 +1,7 @@
import { gettext } from "django"; import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { Application } from "../../api/application"; import { Application } from "../../api/Applications";
import { DefaultClient } from "../../api/client"; import { DefaultClient } from "../../api/Client";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import "../../elements/Tabs"; import "../../elements/Tabs";

View File

@ -1,6 +1,6 @@
import { gettext } from "django"; import { gettext } from "django";
import { customElement, html, property, TemplateResult } from "lit-element"; import { customElement, html, property, TemplateResult } from "lit-element";
import { PBResponse } from "../../api/client"; import { PBResponse } from "../../api/Client";
import { Table } from "../../elements/table/Table"; import { Table } from "../../elements/table/Table";
import "../../elements/Tabs"; import "../../elements/Tabs";
@ -8,7 +8,7 @@ import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/policies/BoundPoliciesList"; import "../../elements/policies/BoundPoliciesList";
import { FlowStageBinding } from "../../api/flow"; import { FlowStageBinding } from "../../api/Flows";
@customElement("ak-bound-stages-list") @customElement("ak-bound-stages-list")
export class BoundStagesList extends Table<FlowStageBinding> { export class BoundStagesList extends Table<FlowStageBinding> {

View File

@ -1,7 +1,7 @@
import { gettext } from "django"; import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { Flow } from "../../api/flow"; import { Flow } from "../../api/Flows";
import "../../elements/Tabs"; import "../../elements/Tabs";
import "../../elements/AdminLoginsChart"; import "../../elements/AdminLoginsChart";

View File

@ -7,7 +7,7 @@ import "../../elements/AdminLoginsChart";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/policies/BoundPoliciesList"; import "../../elements/policies/BoundPoliciesList";
import { Source } from "../../api/source"; import { Source } from "../../api/Sources";
@customElement("ak-source-view") @customElement("ak-source-view")
export class SourceViewPage extends LitElement { export class SourceViewPage extends LitElement {

View File

@ -13,7 +13,7 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/$")).redirect("/library/"), new Route(new RegExp("^/$")).redirect("/library/"),
new Route(new RegExp("^#.*")).redirect("/library/"), new Route(new RegExp("^#.*")).redirect("/library/"),
new Route(new RegExp("^/library/$"), html`<ak-library></ak-library>`), new Route(new RegExp("^/library/$"), html`<ak-library></ak-library>`),
new Route(new RegExp("^/administration/overview-ng/$"), html`<ak-admin-overview></ak-admin-overview>`), new Route(new RegExp("^/administration/overview/$"), html`<ak-admin-overview></ak-admin-overview>`),
new Route(new RegExp("^/applications/$"), html`<ak-application-list></ak-application-list>`), new Route(new RegExp("^/applications/$"), html`<ak-application-list></ak-application-list>`),
new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => { new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
return html`<ak-application-view .args=${args}></ak-application-view>`; return html`<ak-application-view .args=${args}></ak-application-view>`;

View File

@ -15,7 +15,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.0-stable >> .env` To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.2-stable >> .env`
If this is a fresh authentik install run the following commands to generate a password: If this is a fresh authentik install run the following commands to generate a password:

View File

@ -22,7 +22,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: 0.13.0-stable tag: 0.13.2-stable
serverReplicas: 1 serverReplicas: 1
workerReplicas: 1 workerReplicas: 1

View File

@ -35,6 +35,13 @@ If you decided to rename the folder you're running the docker-compose file from,
The only manual change you have to do is replace the `PASSBOOK_` prefix in your `.env` file, so `PASSBOOK_SECRET_KEY` gets changed to `AUTHENTIK_SECRET_KEY`. The only manual change you have to do is replace the `PASSBOOK_` prefix in your `.env` file, so `PASSBOOK_SECRET_KEY` gets changed to `AUTHENTIK_SECRET_KEY`.
Additionally, the database name and username have to be changed, so add this block to your `.env` file:
```
PG_USER=passbook
PG_DB=passbook
```
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`. Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.
### Kubernetes ### Kubernetes

View File

@ -14,7 +14,6 @@ module.exports = {
alt: "authentik logo", alt: "authentik logo",
src: "img/icon_left_brand.svg", src: "img/icon_left_brand.svg",
}, },
hideOnScroll: true,
items: [ items: [
{ {
to: "docs/", to: "docs/",