Compare commits
150 Commits
version/20
...
version-20
Author | SHA1 | Date | |
---|---|---|---|
4b33971155 | |||
9e71287c25 | |||
9784c6c828 | |||
732b6a3556 | |||
dc1e17ba0c | |||
f05d5973af | |||
deb48487f3 | |||
78f3abc64f | |||
e45bc3834a | |||
0d9db1b6f2 | |||
ce555aa5e9 | |||
07ca82e599 | |||
a9339589bb | |||
c8ed650f1c | |||
cd78d8d3fa | |||
7fdc935fb9 | |||
c8069325b3 | |||
9d08e02fe1 | |||
a11ea598a2 | |||
2713b05e8c | |||
fef5a5ca52 | |||
9d339d8b11 | |||
4e86aa3f59 | |||
221e4b665c | |||
e67f235a9f | |||
741ebbacca | |||
b63b789f77 | |||
a63702ef90 | |||
a4a4550753 | |||
fd864655f6 | |||
c1da09507a | |||
ed2ea220bf | |||
7738cbe751 | |||
bf16ea3607 | |||
d6f44e069c | |||
899cf392f4 | |||
d99451b45c | |||
5b31f8edf6 | |||
00235e039b | |||
2dfaef4220 | |||
13fceacfe4 | |||
f8dc32b387 | |||
828f2f8b92 | |||
734399755d | |||
d8f106b976 | |||
9a524dd671 | |||
0775296003 | |||
390534c14e | |||
2a644f64ad | |||
e0298141cf | |||
df7119bb22 | |||
1d5bba831e | |||
0b4be70c00 | |||
786737650b | |||
54c80a2e1f | |||
b376211a0e | |||
1990a3063e | |||
5abf22ad8a | |||
b7b87d87fc | |||
20184424ab | |||
d5de12b69e | |||
d1a3350085 | |||
e0b84c71a7 | |||
3bc1d6a690 | |||
786c74ef2c | |||
3e9b5f5449 | |||
5d071488d3 | |||
90d234a458 | |||
0032bb6aee | |||
6e6755d805 | |||
132b990f10 | |||
34a3d81eff | |||
43a4217497 | |||
e0ec5826ca | |||
5413a01360 | |||
d9c3a29404 | |||
bcce91476c | |||
56f0f454d0 | |||
25e63edf77 | |||
d150851ff5 | |||
2e2840c71e | |||
ff276fcc58 | |||
2852fa3c5e | |||
1c6d498621 | |||
3f0e4bb654 | |||
a59d78a7c7 | |||
0a24202f1e | |||
cbc86d674d | |||
082628771b | |||
93b50e7d6e | |||
c6de4e47d7 | |||
0e9e378bdf | |||
de4b3d6290 | |||
56f75aecc7 | |||
0fe009d37c | |||
49db283e71 | |||
7058366623 | |||
ced45513b8 | |||
15e15c9635 | |||
d53c82eee2 | |||
e1e0b0cf7d | |||
33e013a59f | |||
96a74776f8 | |||
bb63d08682 | |||
32655567da | |||
ff5f5f65e8 | |||
1f97aa09fa | |||
32e5ebb8a3 | |||
597e00dd86 | |||
dd31191845 | |||
e9d95b1311 | |||
3319547a0e | |||
1a00730cdd | |||
466723573c | |||
ea784d47f4 | |||
77d5ba2862 | |||
f4580a1097 | |||
9e3d1f0baa | |||
c002c4b610 | |||
dde5e910cf | |||
5218332bce | |||
28cd08bbba | |||
3cb0575a1e | |||
dc1c1b9569 | |||
662d117b66 | |||
b2449757f9 | |||
a0753bfc88 | |||
e2a771bdaa | |||
23de9df2a5 | |||
5c739ebed2 | |||
d3f8d7120f | |||
21fd251edf | |||
28cededb90 | |||
d420719649 | |||
0018fbacd3 | |||
8c41d2f4cb | |||
3941590d0c | |||
dc4a7c35da | |||
e8c9b70ae8 | |||
74d240dfd4 | |||
7d296b2119 | |||
373793ce9a | |||
5c0ec7554b | |||
792fa45dca | |||
743aaea15e | |||
de03ed0aec | |||
e68ec16a34 | |||
68a0219d0f | |||
38d9533afd | |||
7538af5e09 |
@ -1,9 +1,11 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.3.1-rc1
|
||||
current_version = 2021.3.4
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||
serialize = {major}.{minor}.{patch}-{release}
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
serialize =
|
||||
{major}.{minor}.{patch}-{release}
|
||||
{major}.{minor}.{patch}
|
||||
message = release: {new_version}
|
||||
tag_name = version/{new_version}
|
||||
|
||||
@ -34,3 +36,7 @@ values =
|
||||
[bumpversion:file:outpost/pkg/version.go]
|
||||
|
||||
[bumpversion:file:web/src/constants.ts]
|
||||
|
||||
[bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md]
|
||||
|
||||
[bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md]
|
||||
|
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@ -18,11 +18,11 @@ jobs:
|
||||
- name: Building Docker Image
|
||||
run: docker build
|
||||
--no-cache
|
||||
-t beryju/authentik:2021.3.1-rc1
|
||||
-t beryju/authentik:2021.3.4
|
||||
-t beryju/authentik:latest
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik:2021.3.1-rc1
|
||||
run: docker push beryju/authentik:2021.3.4
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik:latest
|
||||
build-proxy:
|
||||
@ -48,17 +48,20 @@ jobs:
|
||||
cd outpost/
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/authentik-proxy:2021.3.1-rc1 \
|
||||
-t beryju/authentik-proxy:2021.3.4 \
|
||||
-t beryju/authentik-proxy:latest \
|
||||
-f proxy.Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik-proxy:2021.3.1-rc1
|
||||
run: docker push beryju/authentik-proxy:2021.3.4
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik-proxy:latest
|
||||
build-static:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: prepare ts api client
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
|
||||
- name: Docker Login Registry
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
@ -69,11 +72,11 @@ jobs:
|
||||
cd web/
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/authentik-static:2021.3.1-rc1 \
|
||||
-t beryju/authentik-static:2021.3.4 \
|
||||
-t beryju/authentik-static:latest \
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik-static:2021.3.1-rc1
|
||||
run: docker push beryju/authentik-static:2021.3.4
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik-static:latest
|
||||
test-release:
|
||||
@ -107,5 +110,5 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
tagName: 2021.3.1-rc1
|
||||
tagName: 2021.3.4
|
||||
environment: beryjuorg-prod
|
||||
|
@ -15,6 +15,9 @@ WORKDIR /
|
||||
COPY --from=locker /app/requirements.txt /
|
||||
COPY --from=locker /app/requirements-dev.txt /
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends curl ca-certificates gnupg && \
|
||||
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
|
||||
@ -45,4 +48,5 @@ COPY ./lifecycle/ /lifecycle
|
||||
USER authentik
|
||||
STOPSIGNAL SIGINT
|
||||
ENV TMPDIR /dev/shm/
|
||||
ENV PYTHONUBUFFERED 1
|
||||
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]
|
||||
|
192
Pipfile.lock
generated
192
Pipfile.lock
generated
@ -18,45 +18,45 @@
|
||||
"default": {
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0",
|
||||
"sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6",
|
||||
"sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf",
|
||||
"sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9",
|
||||
"sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e",
|
||||
"sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0",
|
||||
"sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329",
|
||||
"sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2",
|
||||
"sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40",
|
||||
"sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a",
|
||||
"sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4",
|
||||
"sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de",
|
||||
"sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9",
|
||||
"sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9",
|
||||
"sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb",
|
||||
"sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076",
|
||||
"sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de",
|
||||
"sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907",
|
||||
"sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d",
|
||||
"sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536",
|
||||
"sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d",
|
||||
"sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54",
|
||||
"sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc",
|
||||
"sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212",
|
||||
"sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9",
|
||||
"sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d",
|
||||
"sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b",
|
||||
"sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7",
|
||||
"sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81",
|
||||
"sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c",
|
||||
"sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895",
|
||||
"sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297",
|
||||
"sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb",
|
||||
"sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe",
|
||||
"sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242",
|
||||
"sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0",
|
||||
"sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"
|
||||
"sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe",
|
||||
"sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe",
|
||||
"sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5",
|
||||
"sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8",
|
||||
"sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd",
|
||||
"sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb",
|
||||
"sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c",
|
||||
"sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87",
|
||||
"sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0",
|
||||
"sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290",
|
||||
"sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5",
|
||||
"sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287",
|
||||
"sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde",
|
||||
"sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf",
|
||||
"sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8",
|
||||
"sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16",
|
||||
"sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf",
|
||||
"sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809",
|
||||
"sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213",
|
||||
"sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f",
|
||||
"sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013",
|
||||
"sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b",
|
||||
"sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9",
|
||||
"sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5",
|
||||
"sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb",
|
||||
"sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df",
|
||||
"sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4",
|
||||
"sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439",
|
||||
"sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f",
|
||||
"sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22",
|
||||
"sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f",
|
||||
"sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5",
|
||||
"sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970",
|
||||
"sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009",
|
||||
"sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc",
|
||||
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
|
||||
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
|
||||
],
|
||||
"version": "==3.7.4"
|
||||
"version": "==3.7.4.post0"
|
||||
},
|
||||
"aioredis": {
|
||||
"hashes": [
|
||||
@ -95,10 +95,10 @@
|
||||
},
|
||||
"autobahn": {
|
||||
"hashes": [
|
||||
"sha256:884f79c50fdc55ade2c315946a9caa145e8b10075eee9d2c2594ea5e8f5226aa",
|
||||
"sha256:bf7a9d302a34d0f719d43c57f65ca1f2f5c982dd6ea0c11e1e190ef6f43710fe"
|
||||
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
|
||||
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
|
||||
],
|
||||
"version": "==21.2.2"
|
||||
"version": "==21.3.1"
|
||||
},
|
||||
"automat": {
|
||||
"hashes": [
|
||||
@ -116,18 +116,17 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:3570a3c0fbd80bcb30449f87cf9d2f7abb67fac2a5e317d002f9921c59be9b17",
|
||||
"sha256:ceff2f32ba05acc9ee35a6dd82e29ea285d63e889bed39a6ba7a700146f43749"
|
||||
"sha256:64a8900b3a110e2d6ff4d87f4d8cd56f0c8527361d9fc9385fcb50efe7a4975a",
|
||||
"sha256:8e9ff8006c41889ed8a11831dee62adf922e071f14d54c52946d1f7855ae7a8e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.18"
|
||||
"version": "==1.17.26"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:51900b10da4ae45be4b16045e5b2ff7d1158a7955d9d7cc5e5a9ba3170f10586",
|
||||
"sha256:b181f32d9075e5419a89fa9636ce95946c15459c9bfadfabb53ca902fc8072b8"
|
||||
"sha256:4a785847a351e59f2329627fc9a19cf50f07644ea68996a1595d5a20487a423f"
|
||||
],
|
||||
"version": "==1.20.18"
|
||||
"version": "==1.20.26"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
@ -217,10 +216,10 @@
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
@ -304,11 +303,11 @@
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
|
||||
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
|
||||
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.6.0"
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
@ -445,10 +444,10 @@
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65",
|
||||
"sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e"
|
||||
"sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f",
|
||||
"sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"
|
||||
],
|
||||
"version": "==1.27.0"
|
||||
"version": "==1.27.1"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
@ -817,10 +816,10 @@
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
|
||||
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
|
||||
"sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a",
|
||||
"sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"
|
||||
],
|
||||
"version": "==3.0.16"
|
||||
"version": "==3.0.17"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
@ -1024,15 +1023,23 @@
|
||||
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
|
||||
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
|
||||
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
|
||||
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
|
||||
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
|
||||
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
|
||||
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
|
||||
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
|
||||
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
|
||||
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
|
||||
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
|
||||
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
|
||||
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
|
||||
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
|
||||
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
|
||||
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
|
||||
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
|
||||
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
|
||||
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
|
||||
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
|
||||
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.4.1"
|
||||
@ -1069,10 +1076,47 @@
|
||||
},
|
||||
"ruamel.yaml": {
|
||||
"hashes": [
|
||||
"sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5",
|
||||
"sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"
|
||||
"sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3",
|
||||
"sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"
|
||||
],
|
||||
"version": "==0.16.12"
|
||||
"version": "==0.16.13"
|
||||
},
|
||||
"ruamel.yaml.clib": {
|
||||
"hashes": [
|
||||
"sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b",
|
||||
"sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f",
|
||||
"sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c",
|
||||
"sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91",
|
||||
"sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc",
|
||||
"sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7",
|
||||
"sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3",
|
||||
"sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7",
|
||||
"sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6",
|
||||
"sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6",
|
||||
"sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd",
|
||||
"sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0",
|
||||
"sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62",
|
||||
"sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99",
|
||||
"sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5",
|
||||
"sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026",
|
||||
"sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb",
|
||||
"sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2",
|
||||
"sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1",
|
||||
"sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4",
|
||||
"sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b",
|
||||
"sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923",
|
||||
"sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e",
|
||||
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
|
||||
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
|
||||
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
|
||||
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
|
||||
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
|
||||
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
|
||||
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
|
||||
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
|
||||
],
|
||||
"markers": "platform_python_implementation == 'CPython' and python_version < '3.10'",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
@ -1083,11 +1127,11 @@
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
"sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237",
|
||||
"sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b"
|
||||
"sha256:71de00c9711926816f750bc0f57ef2abbcb1bfbdf5378c601df7ec978f44857a",
|
||||
"sha256:9221e985f425913204989d0e0e1cbb719e8b7fa10540f1bc509f660c06a34e66"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.20.3"
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
@ -1249,10 +1293,10 @@
|
||||
},
|
||||
"websocket-client": {
|
||||
"hashes": [
|
||||
"sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549",
|
||||
"sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"
|
||||
"sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663",
|
||||
"sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"
|
||||
],
|
||||
"version": "==0.57.0"
|
||||
"version": "==0.58.0"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
@ -1765,15 +1809,23 @@
|
||||
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
|
||||
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
|
||||
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
|
||||
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
|
||||
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
|
||||
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
|
||||
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
|
||||
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
|
||||
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
|
||||
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
|
||||
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
|
||||
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
|
||||
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
|
||||
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
|
||||
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
|
||||
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
|
||||
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
|
||||
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
|
||||
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
|
||||
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
|
||||
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.4.1"
|
||||
|
10
README.md
10
README.md
@ -1,4 +1,6 @@
|
||||
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo">
|
||||
<p align="center">
|
||||
<img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
@ -22,8 +24,10 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
Light | Dark
|
||||
--- | ---
|
||||
 | 
|
||||
 | 
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ---------- | ------------------ |
|
||||
| 0.13.x | :white_check_mark: |
|
||||
| 0.14.x | :white_check_mark: |
|
||||
| 2021.1.x | :white_check_mark: |
|
||||
| 2021.2.x | :white_check_mark: |
|
||||
| 2021.3.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.3.1-rc1"
|
||||
__version__ = "2021.3.4"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -7,8 +7,8 @@ from django.db.models import Count, ExpressionWrapper, F, Model
|
||||
from django.db.models.fields import DurationField
|
||||
from django.db.models.functions import ExtractHour
|
||||
from django.utils.timezone import now
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
|
||||
from rest_framework.fields import IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -37,23 +37,39 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
|
||||
for hour in range(0, -24, -1):
|
||||
results.append(
|
||||
{
|
||||
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
|
||||
"y": data[hour * -1],
|
||||
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
|
||||
* 1000,
|
||||
"y_cord": data[hour * -1],
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
class AdministrationMetricsSerializer(Serializer):
|
||||
class CoordinateSerializer(Serializer):
|
||||
"""Coordinates for diagrams"""
|
||||
|
||||
x_cord = IntegerField(read_only=True)
|
||||
y_cord = IntegerField(read_only=True)
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LoginMetricsSerializer(Serializer):
|
||||
"""Login Metrics per 1h"""
|
||||
|
||||
logins_per_1h = SerializerMethodField()
|
||||
logins_failed_per_1h = SerializerMethodField()
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||
def get_logins_per_1h(self, _):
|
||||
"""Get successful logins per hour for the last 24 hours"""
|
||||
return get_events_per_1h(action=EventAction.LOGIN)
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
|
||||
def get_logins_failed_per_1h(self, _):
|
||||
"""Get failed logins per hour for the last 24 hours"""
|
||||
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
|
||||
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Login Metrics per 1h"""
|
||||
serializer = AdministrationMetricsSerializer(True)
|
||||
serializer = LoginMetricsSerializer(True)
|
||||
return Response(serializer.data)
|
||||
|
@ -7,14 +7,14 @@ from django.http.response import Http404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
|
||||
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
|
||||
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.events.monitored_tasks import TaskInfo
|
||||
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
|
||||
|
||||
|
||||
class TaskSerializer(Serializer):
|
||||
@ -24,7 +24,10 @@ class TaskSerializer(Serializer):
|
||||
task_description = CharField()
|
||||
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
||||
|
||||
status = IntegerField(source="result.status.value")
|
||||
status = ChoiceField(
|
||||
source="result.status.name",
|
||||
choices=[(x.name, x.name) for x in TaskResultStatus],
|
||||
)
|
||||
messages = ListField(source="result.messages")
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""authentik administration overview"""
|
||||
from os import environ
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Model
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
@ -11,7 +13,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.serializers import Serializer
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik import __version__
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
|
||||
|
||||
|
||||
@ -20,8 +22,13 @@ class VersionSerializer(Serializer):
|
||||
|
||||
version_current = SerializerMethodField()
|
||||
version_latest = SerializerMethodField()
|
||||
build_hash = SerializerMethodField()
|
||||
outdated = SerializerMethodField()
|
||||
|
||||
def get_build_hash(self, _) -> str:
|
||||
"""Get build hash, if version is not latest or released"""
|
||||
return environ.get(ENV_GIT_HASH_KEY, "")
|
||||
|
||||
def get_version_current(self, _) -> str:
|
||||
"""Get current version"""
|
||||
return __version__
|
||||
@ -55,7 +62,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet):
|
||||
def get_queryset(self): # pragma: no cover
|
||||
return None
|
||||
|
||||
@swagger_auto_schema(responses={200: VersionSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: VersionSerializer(many=False)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Get running and latest version."""
|
||||
return Response(VersionSerializer(True).data)
|
||||
|
@ -27,7 +27,9 @@
|
||||
</div>
|
||||
</section>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" />
|
||||
<ak-spinner-button form="main-form">
|
||||
{% block action %}{% endblock %}
|
||||
</ak-spinner-button>
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
@ -6,6 +6,7 @@ from rest_framework.response import Response
|
||||
class Pagination(pagination.PageNumberPagination):
|
||||
"""Pagination which includes total pages and current page"""
|
||||
|
||||
page_query_param = "page"
|
||||
page_size_query_param = "page_size"
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
97
authentik/api/pagination_schema.py
Normal file
97
authentik/api/pagination_schema.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""Swagger Pagination Schema class"""
|
||||
from typing import OrderedDict
|
||||
|
||||
from drf_yasg2 import openapi
|
||||
from drf_yasg2.inspectors import PaginatorInspector
|
||||
|
||||
|
||||
class PaginationInspector(PaginatorInspector):
|
||||
"""Swagger Pagination Schema class"""
|
||||
|
||||
def get_paginated_response(self, paginator, response_schema):
|
||||
"""
|
||||
:param BasePagination paginator: the paginator
|
||||
:param openapi.Schema response_schema: the response schema that must be paged.
|
||||
:rtype: openapi.Schema
|
||||
"""
|
||||
|
||||
return openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties=OrderedDict(
|
||||
(
|
||||
(
|
||||
"pagination",
|
||||
openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties=OrderedDict(
|
||||
(
|
||||
("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
|
||||
(
|
||||
"previous",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
|
||||
(
|
||||
"current",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"total_pages",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"start_index",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
(
|
||||
"end_index",
|
||||
openapi.Schema(type=openapi.TYPE_NUMBER),
|
||||
),
|
||||
)
|
||||
),
|
||||
required=[
|
||||
"next",
|
||||
"previous",
|
||||
"count",
|
||||
"current",
|
||||
"total_pages",
|
||||
"start_index",
|
||||
"end_index",
|
||||
],
|
||||
),
|
||||
),
|
||||
("results", response_schema),
|
||||
)
|
||||
),
|
||||
required=["results", "pagination"],
|
||||
)
|
||||
|
||||
def get_paginator_parameters(self, paginator):
|
||||
"""
|
||||
Get the pagination parameters for a single paginator **instance**.
|
||||
|
||||
Should return :data:`.NotHandled` if this inspector
|
||||
does not know how to handle the given `paginator`.
|
||||
|
||||
:param BasePagination paginator: the paginator
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
|
||||
return [
|
||||
openapi.Parameter(
|
||||
"page",
|
||||
openapi.IN_QUERY,
|
||||
"Page Index",
|
||||
False,
|
||||
None,
|
||||
openapi.TYPE_INTEGER,
|
||||
),
|
||||
openapi.Parameter(
|
||||
"page_size",
|
||||
openapi.IN_QUERY,
|
||||
"Page Size",
|
||||
False,
|
||||
None,
|
||||
openapi.TYPE_INTEGER,
|
||||
),
|
||||
]
|
@ -1,10 +1,11 @@
|
||||
"""core Configs API"""
|
||||
from django.db.models import Model
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ReadOnlyField, Serializer
|
||||
from rest_framework.serializers import Serializer
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
@ -13,12 +14,12 @@ from authentik.lib.config import CONFIG
|
||||
class ConfigSerializer(Serializer):
|
||||
"""Serialize authentik Config into DRF Object"""
|
||||
|
||||
branding_logo = ReadOnlyField()
|
||||
branding_title = ReadOnlyField()
|
||||
branding_logo = CharField(read_only=True)
|
||||
branding_title = CharField(read_only=True)
|
||||
|
||||
error_reporting_enabled = ReadOnlyField()
|
||||
error_reporting_environment = ReadOnlyField()
|
||||
error_reporting_send_pii = ReadOnlyField()
|
||||
error_reporting_enabled = BooleanField(read_only=True)
|
||||
error_reporting_environment = CharField(read_only=True)
|
||||
error_reporting_send_pii = BooleanField(read_only=True)
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
@ -32,7 +33,7 @@ class ConfigsViewSet(ViewSet):
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(responses={200: ConfigSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: ConfigSerializer(many=False)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Retrive public configuration options"""
|
||||
config = ConfigSerializer(
|
||||
|
@ -1,37 +0,0 @@
|
||||
"""core messages API"""
|
||||
from django.contrib.messages import get_messages
|
||||
from django.db.models import Model
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ReadOnlyField, Serializer
|
||||
from rest_framework.viewsets import ViewSet
|
||||
|
||||
|
||||
class MessageSerializer(Serializer):
|
||||
"""Serialize Django Message into DRF Object"""
|
||||
|
||||
message = ReadOnlyField()
|
||||
level = ReadOnlyField()
|
||||
tags = ReadOnlyField()
|
||||
extra_tags = ReadOnlyField()
|
||||
level_tag = ReadOnlyField()
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, instance: Model, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MessagesViewSet(ViewSet):
|
||||
"""Read-only view set that returns the current session's messages"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@swagger_auto_schema(responses={200: MessageSerializer(many=True)})
|
||||
def list(self, request: Request) -> Response:
|
||||
"""List current messages and pass into Serializer"""
|
||||
all_messages = list(get_messages(request))
|
||||
return Response(MessageSerializer(all_messages, many=True).data)
|
@ -1,4 +1,5 @@
|
||||
"""api v2 urls"""
|
||||
from django.conf import settings
|
||||
from django.urls import path, re_path
|
||||
from drf_yasg2 import openapi
|
||||
from drf_yasg2.views import get_schema_view
|
||||
@ -10,7 +11,6 @@ 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.messages import MessagesViewSet
|
||||
from authentik.core.api.applications import ApplicationViewSet
|
||||
from authentik.core.api.groups import GroupViewSet
|
||||
from authentik.core.api.propertymappings import PropertyMappingViewSet
|
||||
@ -55,12 +55,24 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide
|
||||
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
||||
from authentik.sources.oauth.api import OAuthSourceViewSet
|
||||
from authentik.sources.saml.api import SAMLSourceViewSet
|
||||
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet
|
||||
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet
|
||||
from authentik.stages.authenticator_static.api import (
|
||||
AuthenticatorStaticStageViewSet,
|
||||
StaticAdminDeviceViewSet,
|
||||
StaticDeviceViewSet,
|
||||
)
|
||||
from authentik.stages.authenticator_totp.api import (
|
||||
AuthenticatorTOTPStageViewSet,
|
||||
TOTPAdminDeviceViewSet,
|
||||
TOTPDeviceViewSet,
|
||||
)
|
||||
from authentik.stages.authenticator_validate.api import (
|
||||
AuthenticatorValidateStageViewSet,
|
||||
)
|
||||
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet
|
||||
from authentik.stages.authenticator_webauthn.api import (
|
||||
AuthenticateWebAuthnStageViewSet,
|
||||
WebAuthnAdminDeviceViewSet,
|
||||
WebAuthnDeviceViewSet,
|
||||
)
|
||||
from authentik.stages.captcha.api import CaptchaStageViewSet
|
||||
from authentik.stages.consent.api import ConsentStageViewSet
|
||||
from authentik.stages.deny.api import DenyStageViewSet
|
||||
@ -77,7 +89,6 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
router.register("root/messages", MessagesViewSet, basename="messages")
|
||||
router.register("root/config", ConfigsViewSet, basename="configs")
|
||||
|
||||
router.register("admin/version", VersionViewSet, basename="admin_version")
|
||||
@ -135,6 +146,13 @@ router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
|
||||
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
|
||||
router.register("propertymappings/scope", ScopeMappingViewSet)
|
||||
|
||||
router.register("authenticators/static", StaticDeviceViewSet)
|
||||
router.register("authenticators/totp", TOTPDeviceViewSet)
|
||||
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
|
||||
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
|
||||
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
|
||||
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)
|
||||
|
||||
router.register("stages/all", StageViewSet)
|
||||
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
|
||||
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
|
||||
@ -166,27 +184,26 @@ info = openapi.Info(
|
||||
name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
|
||||
),
|
||||
)
|
||||
SchemaView = get_schema_view(
|
||||
info,
|
||||
public=True,
|
||||
permission_classes=(AllowAny,),
|
||||
)
|
||||
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
|
||||
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
SchemaView.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
path(
|
||||
"swagger/",
|
||||
SchemaView.with_ui("swagger", cache_timeout=0),
|
||||
name="schema-swagger-ui",
|
||||
),
|
||||
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
|
||||
urlpatterns = router.urls + [
|
||||
path(
|
||||
"flows/executor/<slug:flow_slug>/",
|
||||
FlowExecutorView.as_view(),
|
||||
name="flow-executor",
|
||||
),
|
||||
] + router.urls
|
||||
re_path(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
SchemaView.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns = urlpatterns + [
|
||||
path(
|
||||
"swagger/",
|
||||
SchemaView.with_ui("swagger", cache_timeout=0),
|
||||
name="schema-swagger-ui",
|
||||
),
|
||||
]
|
||||
|
@ -2,6 +2,7 @@
|
||||
from django.core.cache import cache
|
||||
from django.db.models import QuerySet
|
||||
from django.http.response import Http404
|
||||
from drf_yasg2.utils import swagger_auto_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
@ -13,7 +14,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.admin.api.metrics import get_events_per_1h
|
||||
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.models import Application
|
||||
from authentik.events.models import EventAction
|
||||
@ -109,6 +110,7 @@ class ApplicationViewSet(ModelViewSet):
|
||||
serializer = self.get_serializer(allowed_applications, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
|
||||
@action(detail=True)
|
||||
def metrics(self, request: Request, slug: str):
|
||||
"""Metrics for application logins"""
|
||||
|
@ -21,3 +21,4 @@ class GroupViewSet(ModelViewSet):
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_fields = ["name", "is_superuser"]
|
||||
ordering = ["name"]
|
||||
|
@ -28,9 +28,9 @@ class MetaNameSerializer(Serializer):
|
||||
class TypeCreateSerializer(Serializer):
|
||||
"""Types of an object that can be created"""
|
||||
|
||||
name = CharField(read_only=True)
|
||||
description = CharField(read_only=True)
|
||||
link = CharField(read_only=True)
|
||||
name = CharField(required=True)
|
||||
description = CharField(required=True)
|
||||
link = CharField(required=True)
|
||||
|
||||
def create(self, validated_data: dict) -> Model:
|
||||
raise NotImplementedError
|
||||
|
@ -90,7 +90,7 @@ class UserManager(DjangoUserManager):
|
||||
|
||||
|
||||
class User(GuardianUserMixin, AbstractUser):
|
||||
"""Custom User model to allow easier adding o f user-based settings"""
|
||||
"""Custom User model to allow easier adding of user-based settings"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||
name = models.TextField(help_text=_("User's display name."))
|
||||
|
@ -46,8 +46,7 @@ def backup_database(self: MonitoredTask): # pragma: no cover
|
||||
TaskResult(
|
||||
TaskResultStatus.SUCCESSFUL,
|
||||
[
|
||||
f"Successfully finished database backup {naturaltime(start)}",
|
||||
out.getvalue(),
|
||||
f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -1,12 +0,0 @@
|
||||
{% extends "base/skeleton.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block body %}
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-page">
|
||||
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
|
||||
{% block page_content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -11,9 +11,7 @@
|
||||
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
|
||||
<link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-addons.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/fontawesome.min.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}">
|
||||
<script src="{% url 'javascript-catalog' %}?v={{ ak_version }}"></script>
|
||||
{% block head %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'base/page.html' %}
|
||||
{% extends 'base/skeleton.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
@ -1,9 +1,6 @@
|
||||
{% extends container_template|default:"administration/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
{% block content %}
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
{% block above_form %}
|
||||
@ -38,4 +35,3 @@
|
||||
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay">
|
||||
<feColorMatrix type="matrix" values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0"></feColorMatrix>
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="1.3 0 0 0 0 0 1.3 0 0 0 0 0 1.3 0 0 0 0 0 1 0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
@ -48,7 +48,7 @@
|
||||
{% endfor %}
|
||||
{% if config.authentik.branding.title != "authentik" %}
|
||||
<li>
|
||||
<a href="https://github.com/beryju/authentik">
|
||||
<a href="https://goauthentik.io">
|
||||
{% trans 'Powered by authentik' %}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -13,7 +13,7 @@
|
||||
<p>{% trans "Configure settings relevant to your user profile." %}</p>
|
||||
</div>
|
||||
</section>
|
||||
<ak-tabs>
|
||||
<ak-tabs vertical="true" style="height: 100%;">
|
||||
<section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
|
@ -62,7 +62,7 @@ class TokenCreateView(
|
||||
permission_required = "authentik_core.add_token"
|
||||
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully created Token")
|
||||
|
||||
def form_valid(self, form: UserTokenForm) -> HttpResponse:
|
||||
@ -80,7 +80,7 @@ class TokenUpdateView(
|
||||
form_class = UserTokenForm
|
||||
permission_required = "authentik_core.change_token"
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully updated Token")
|
||||
|
||||
def get_object(self) -> Token:
|
||||
@ -100,7 +100,7 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
|
||||
model = Token
|
||||
permission_required = "authentik_core.delete_token"
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("authentik_core:user-tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully deleted Token")
|
||||
|
||||
def get_object(self) -> Token:
|
||||
|
@ -29,7 +29,7 @@ class EventSerializer(ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class EventTopPerUserSerialier(Serializer):
|
||||
class EventTopPerUserSerializer(Serializer):
|
||||
"""Response object of Event's top_per_user"""
|
||||
|
||||
application = DictField()
|
||||
@ -60,7 +60,7 @@ class EventViewSet(ReadOnlyModelViewSet):
|
||||
filterset_fields = ["action"]
|
||||
|
||||
@swagger_auto_schema(
|
||||
method="GET", responses={200: EventTopPerUserSerialier(many=True)}
|
||||
method="GET", responses={200: EventTopPerUserSerializer(many=True)}
|
||||
)
|
||||
@action(detail=False, methods=["GET"])
|
||||
def top_per_user(self, request: Request):
|
||||
|
@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):
|
||||
|
||||
body = ReadOnlyField()
|
||||
severity = ReadOnlyField()
|
||||
event = EventSerializer()
|
||||
event = EventSerializer(required=False)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
"""NotificationTransport API Views"""
|
||||
from django.http.response import Http404
|
||||
from drf_yasg2.utils import no_body, swagger_auto_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.serializers import ModelSerializer, Serializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.events.models import (
|
||||
@ -38,12 +39,28 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class NotificationTransportTestSerializer(Serializer):
|
||||
"""Notification test serializer"""
|
||||
|
||||
messages = ListField(child=CharField())
|
||||
|
||||
def create(self, request: Request) -> Response:
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, request: Request) -> Response:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NotificationTransportViewSet(ModelViewSet):
|
||||
"""NotificationTransport Viewset"""
|
||||
|
||||
queryset = NotificationTransport.objects.all()
|
||||
serializer_class = NotificationTransportSerializer
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={200: NotificationTransportTestSerializer(many=False)},
|
||||
request_body=no_body,
|
||||
)
|
||||
@action(detail=True, methods=["post"])
|
||||
# pylint: disable=invalid-name
|
||||
def test(self, request: Request, pk=None) -> Response:
|
||||
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
|
||||
user=request.user,
|
||||
)
|
||||
try:
|
||||
return Response(transport.send(notification))
|
||||
response = NotificationTransportTestSerializer(
|
||||
data={"messages": transport.send(notification)}
|
||||
)
|
||||
response.is_valid()
|
||||
return Response(response.data)
|
||||
except NotificationTransportError as exc:
|
||||
return Response(str(exc.__cause__ or None), status=503)
|
||||
|
@ -12,7 +12,10 @@ def get_geoip_reader() -> Optional[Reader]:
|
||||
path = CONFIG.y("authentik.geoip")
|
||||
if path == "" or not path:
|
||||
return None
|
||||
return Reader(path)
|
||||
try:
|
||||
return Reader(path)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
GEOIP_READER = get_geoip_reader()
|
||||
|
@ -38,7 +38,9 @@ class Challenge(Serializer):
|
||||
"""Challenge that gets sent to the client based on which stage
|
||||
is currently active"""
|
||||
|
||||
type = ChoiceField(choices=list(ChallengeTypes))
|
||||
type = ChoiceField(
|
||||
choices=[(x.name, x.name) for x in ChallengeTypes],
|
||||
)
|
||||
component = CharField(required=False)
|
||||
title = CharField(required=False)
|
||||
|
||||
@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):
|
||||
|
||||
stage: Optional["StageView"]
|
||||
|
||||
def __init__(self, instance, data, **kwargs):
|
||||
def __init__(self, instance=None, data=None, **kwargs):
|
||||
self.stage = kwargs.pop("stage", None)
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
|
@ -4,6 +4,7 @@ from django.http import HttpRequest
|
||||
from django.http.request import QueryDict
|
||||
from django.http.response import HttpResponse
|
||||
from django.views.generic.base import View
|
||||
from rest_framework.request import Request
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import DEFAULT_AVATAR, User
|
||||
@ -67,9 +68,9 @@ class ChallengeStageView(StageView):
|
||||
return HttpChallengeResponse(challenge)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
def post(self, request: Request, *args, **kwargs) -> HttpResponse:
|
||||
"""Handle challenge response"""
|
||||
challenge: ChallengeResponse = self.get_response_instance(data=request.POST)
|
||||
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
||||
if not challenge.is_valid():
|
||||
return self.challenge_invalid(challenge)
|
||||
return self.challenge_valid(challenge)
|
||||
|
@ -9,11 +9,16 @@ from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||
from django.views.generic import TemplateView, View
|
||||
from drf_yasg2.utils import no_body, swagger_auto_schema
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.views import APIView
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
from authentik.events.models import cleanse_dict
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
ChallengeResponse,
|
||||
ChallengeTypes,
|
||||
HttpChallengeResponse,
|
||||
RedirectChallenge,
|
||||
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
|
||||
|
||||
|
||||
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
||||
class FlowExecutorView(View):
|
||||
class FlowExecutorView(APIView):
|
||||
"""Stage 1 Flow executor, passing requests to Stage Views"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
flow: Flow
|
||||
|
||||
plan: Optional[FlowPlan] = None
|
||||
@ -113,8 +120,13 @@ class FlowExecutorView(View):
|
||||
self.current_stage_view.request = request
|
||||
return super().dispatch(request)
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={200: Challenge()},
|
||||
request_body=no_body,
|
||||
operation_id="flows_executor_get",
|
||||
)
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""pass get request to current stage"""
|
||||
"""Get the next pending challenge from the currently active flow."""
|
||||
self._logger.debug(
|
||||
"f(exec): Passing GET",
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
@ -127,8 +139,13 @@ class FlowExecutorView(View):
|
||||
self._logger.exception(exc)
|
||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={200: Challenge()},
|
||||
request_body=ChallengeResponse(),
|
||||
operation_id="flows_executor_solve",
|
||||
)
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""pass post request to current stage"""
|
||||
"""Solve the previously retrieved challenge and advanced to the next stage."""
|
||||
self._logger.debug(
|
||||
"f(exec): Passing POST",
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
@ -175,8 +192,10 @@ class FlowExecutorView(View):
|
||||
"f(exec): Continuing with next stage",
|
||||
reamining=len(self.plan.stages),
|
||||
)
|
||||
kwargs = self.kwargs
|
||||
kwargs.update({"flow_slug": self.flow.slug})
|
||||
return redirect_with_qs(
|
||||
"authentik_api:flow-executor", self.request.GET, **self.kwargs
|
||||
"authentik_api:flow-executor", self.request.GET, **kwargs
|
||||
)
|
||||
# User passed all stages
|
||||
self._logger.debug(
|
||||
|
@ -13,6 +13,7 @@ redis:
|
||||
ws_db: 2
|
||||
|
||||
debug: false
|
||||
|
||||
log_level: info
|
||||
|
||||
# Error reporting, sends stacktrace to sentry.beryju.org
|
||||
|
@ -2,8 +2,8 @@
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.urls import NoReverseMatch
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils.http import urlencode
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
@ -55,13 +55,21 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
OutpostState(
|
||||
uid=self.channel_name, last_seen=datetime.now(), _outpost=self.outpost
|
||||
).save(timeout=OUTPOST_HELLO_INTERVAL * 1.5)
|
||||
LOGGER.debug("added channel to cache", channel_name=self.channel_name)
|
||||
LOGGER.debug(
|
||||
"added outpost instace to cache",
|
||||
outpost=self.outpost,
|
||||
channel_name=self.channel_name,
|
||||
)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def disconnect(self, close_code):
|
||||
if self.outpost:
|
||||
OutpostState.for_channel(self.outpost, self.channel_name).delete()
|
||||
LOGGER.debug("removed channel from cache", channel_name=self.channel_name)
|
||||
LOGGER.debug(
|
||||
"removed outpost instance from cache",
|
||||
outpost=self.outpost,
|
||||
channel_name=self.channel_name,
|
||||
)
|
||||
|
||||
def receive_json(self, content: Data):
|
||||
msg = from_dict(WebsocketMessage, content)
|
||||
|
@ -5,6 +5,7 @@ from typing import Type
|
||||
from kubernetes.client import OpenApiException
|
||||
from kubernetes.client.api_client import ApiClient
|
||||
from structlog.testing import capture_logs
|
||||
from urllib3.exceptions import HTTPError
|
||||
from yaml import dump_all
|
||||
|
||||
from authentik.outposts.controllers.base import BaseController, ControllerException
|
||||
@ -42,7 +43,7 @@ class KubernetesController(BaseController):
|
||||
reconciler = self.reconcilers[reconcile_key](self)
|
||||
reconciler.up()
|
||||
|
||||
except OpenApiException as exc:
|
||||
except (OpenApiException, HTTPError) as exc:
|
||||
raise ControllerException from exc
|
||||
|
||||
def up_with_logs(self) -> list[str]:
|
||||
@ -54,7 +55,7 @@ class KubernetesController(BaseController):
|
||||
reconciler.up()
|
||||
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
|
||||
return all_logs
|
||||
except OpenApiException as exc:
|
||||
except (OpenApiException, HTTPError) as exc:
|
||||
raise ControllerException from exc
|
||||
|
||||
def down(self):
|
||||
|
@ -0,0 +1,87 @@
|
||||
# Generated by Django 3.1.7 on 2021-03-02 08:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="eventmatcherpolicy",
|
||||
name="app",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("authentik.admin", "authentik Admin"),
|
||||
("authentik.api", "authentik API"),
|
||||
("authentik.events", "authentik Events"),
|
||||
("authentik.crypto", "authentik Crypto"),
|
||||
("authentik.flows", "authentik Flows"),
|
||||
("authentik.outposts", "authentik Outpost"),
|
||||
("authentik.lib", "authentik lib"),
|
||||
("authentik.policies", "authentik Policies"),
|
||||
("authentik.policies.dummy", "authentik Policies.Dummy"),
|
||||
(
|
||||
"authentik.policies.event_matcher",
|
||||
"authentik Policies.Event Matcher",
|
||||
),
|
||||
("authentik.policies.expiry", "authentik Policies.Expiry"),
|
||||
("authentik.policies.expression", "authentik Policies.Expression"),
|
||||
(
|
||||
"authentik.policies.group_membership",
|
||||
"authentik Policies.Group Membership",
|
||||
),
|
||||
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
|
||||
("authentik.policies.password", "authentik Policies.Password"),
|
||||
("authentik.policies.reputation", "authentik Policies.Reputation"),
|
||||
("authentik.providers.proxy", "authentik Providers.Proxy"),
|
||||
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
|
||||
("authentik.providers.saml", "authentik Providers.SAML"),
|
||||
("authentik.recovery", "authentik Recovery"),
|
||||
("authentik.sources.ldap", "authentik Sources.LDAP"),
|
||||
("authentik.sources.oauth", "authentik Sources.OAuth"),
|
||||
("authentik.sources.saml", "authentik Sources.SAML"),
|
||||
(
|
||||
"authentik.stages.authenticator_static",
|
||||
"authentik Stages.Authenticator.Static",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_totp",
|
||||
"authentik Stages.Authenticator.TOTP",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_validate",
|
||||
"authentik Stages.Authenticator.Validate",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_webauthn",
|
||||
"authentik Stages.Authenticator.WebAuthn",
|
||||
),
|
||||
("authentik.stages.captcha", "authentik Stages.Captcha"),
|
||||
("authentik.stages.consent", "authentik Stages.Consent"),
|
||||
("authentik.stages.deny", "authentik Stages.Deny"),
|
||||
("authentik.stages.dummy", "authentik Stages.Dummy"),
|
||||
("authentik.stages.email", "authentik Stages.Email"),
|
||||
(
|
||||
"authentik.stages.identification",
|
||||
"authentik Stages.Identification",
|
||||
),
|
||||
("authentik.stages.invitation", "authentik Stages.User Invitation"),
|
||||
("authentik.stages.password", "authentik Stages.Password"),
|
||||
("authentik.stages.prompt", "authentik Stages.Prompt"),
|
||||
("authentik.stages.user_delete", "authentik Stages.User Delete"),
|
||||
("authentik.stages.user_login", "authentik Stages.User Login"),
|
||||
("authentik.stages.user_logout", "authentik Stages.User Logout"),
|
||||
("authentik.stages.user_write", "authentik Stages.User Write"),
|
||||
("authentik.managed", "authentik Managed"),
|
||||
("authentik.core", "authentik Core"),
|
||||
],
|
||||
default="",
|
||||
help_text="Match events created by selected application. When left empty, all applications are matched.",
|
||||
),
|
||||
),
|
||||
]
|
@ -26,5 +26,5 @@ def invalidate_policy_cache(sender, instance, **_):
|
||||
cache.delete_many(keys)
|
||||
LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
|
||||
# Also delete user application cache
|
||||
keys = cache.keys(user_app_cache_key("*"))
|
||||
keys = cache.keys(user_app_cache_key("*")) or []
|
||||
cache.delete_many(keys)
|
||||
|
@ -62,11 +62,15 @@ class PolicyAccessView(AccessMixin, View):
|
||||
return self.handle_no_permission()
|
||||
try:
|
||||
self.resolve_provider_application()
|
||||
except (Application.DoesNotExist, Provider.DoesNotExist):
|
||||
return self.handle_no_permission_authenticated()
|
||||
except (Application.DoesNotExist, Provider.DoesNotExist) as exc:
|
||||
LOGGER.warning("failed to resolve application", exc=exc)
|
||||
return self.handle_no_permission_authenticated(
|
||||
PolicyResult(False, _("Failed to resolve application"))
|
||||
)
|
||||
# Check if user is unauthenticated, so we pass the application
|
||||
# for the identification stage
|
||||
if not request.user.is_authenticated:
|
||||
LOGGER.warning("user not authenticated")
|
||||
return self.handle_no_permission()
|
||||
# Check permissions
|
||||
result = self.user_has_access()
|
||||
|
@ -45,6 +45,7 @@ class OAuth2ProviderSetupURLs(Serializer):
|
||||
token = ReadOnlyField()
|
||||
user_info = ReadOnlyField()
|
||||
provider_info = ReadOnlyField()
|
||||
logout = ReadOnlyField()
|
||||
|
||||
def create(self, request: Request) -> Response:
|
||||
raise NotImplementedError
|
||||
@ -83,6 +84,7 @@ class OAuth2ProviderViewSet(ModelViewSet):
|
||||
)
|
||||
),
|
||||
"provider_info": None,
|
||||
"logout": None,
|
||||
}
|
||||
try:
|
||||
data["provider_info"] = request.build_absolute_uri(
|
||||
@ -91,6 +93,12 @@ class OAuth2ProviderViewSet(ModelViewSet):
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
data["logout"] = request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_oauth2:end-session",
|
||||
kwargs={"application_slug": provider.application.slug},
|
||||
)
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
|
||||
pass
|
||||
return Response(data)
|
||||
|
@ -101,7 +101,9 @@ def protected_resource_view(scopes: list[str]):
|
||||
This decorator also injects the token into `kwargs`"""
|
||||
|
||||
def wrapper(view):
|
||||
def view_wrapper(request, *args, **kwargs):
|
||||
def view_wrapper(request: HttpRequest, *args, **kwargs):
|
||||
if request.method == "OPTIONS":
|
||||
return view(request, *args, **kwargs)
|
||||
try:
|
||||
access_token = extract_access_token(request)
|
||||
if not access_token:
|
||||
|
@ -19,6 +19,7 @@ from authentik.providers.oauth2.models import (
|
||||
ResponseTypes,
|
||||
ScopeMapping,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import cors_allow_any
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -103,9 +104,10 @@ class ProviderInfoView(View):
|
||||
provider: OAuth2Provider = get_object_or_404(
|
||||
OAuth2Provider, pk=application.provider_id
|
||||
)
|
||||
response = JsonResponse(
|
||||
self.get_info(provider), json_dumps_params={"indent": 2}
|
||||
)
|
||||
response["Access-Control-Allow-Origin"] = "*"
|
||||
return JsonResponse(self.get_info(provider), json_dumps_params={"indent": 2})
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
# Since this view only supports get, we can statically set the CORS headers
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
cors_allow_any(request, response)
|
||||
return response
|
||||
|
@ -74,7 +74,7 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||
return super().get(
|
||||
self.request,
|
||||
**{
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-autosubmit",
|
||||
"title": "Redirecting to %(app)s..." % {"app": application.name},
|
||||
"url": provider.acs_url,
|
||||
|
@ -24,7 +24,7 @@ from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
from authentik import __version__
|
||||
from authentik import ENV_GIT_HASH_KEY, __version__
|
||||
from authentik.core.middleware import structlog_add_request_id
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.logging import add_process_id
|
||||
@ -139,6 +139,9 @@ GUARDIAN_MONKEY_PATCH = False
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
"DEFAULT_INFO": "authentik.api.v2.urls.info",
|
||||
"DEFAULT_PAGINATOR_INSPECTORS": [
|
||||
"authentik.api.pagination_schema.PaginationInspector",
|
||||
],
|
||||
"SECURITY_DEFINITIONS": {
|
||||
"token": {"type": "apiKey", "name": "Authorization", "in": "header"}
|
||||
},
|
||||
@ -147,7 +150,6 @@ SWAGGER_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
|
||||
"PAGE_SIZE": 100,
|
||||
"DATETIME_FORMAT": "%s",
|
||||
"DEFAULT_FILTER_BACKENDS": [
|
||||
"rest_framework_guardian.filters.ObjectPermissionsFilter",
|
||||
"django_filters.rest_framework.DjangoFilterBackend",
|
||||
@ -472,6 +474,7 @@ for _app in INSTALLED_APPS:
|
||||
|
||||
if DEBUG:
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
os.environ[ENV_GIT_HASH_KEY] = "dev"
|
||||
|
||||
INSTALLED_APPS.append("authentik.core.apps.AuthentikCoreConfig")
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
"""Sync LDAP Users into authentik"""
|
||||
from datetime import datetime
|
||||
|
||||
import ldap3
|
||||
import ldap3.core.exceptions
|
||||
from django.db.utils import IntegrityError
|
||||
from pytz import UTC
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
|
||||
@ -53,11 +56,19 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
)
|
||||
)
|
||||
else:
|
||||
if created:
|
||||
ak_user.set_unusable_password()
|
||||
ak_user.save()
|
||||
self._logger.debug(
|
||||
"Synced User", user=ak_user.username, created=created
|
||||
)
|
||||
user_count += 1
|
||||
pwd_last_set: datetime = attributes.get("pwdLastSet", datetime.now())
|
||||
pwd_last_set = pwd_last_set.replace(tzinfo=UTC)
|
||||
if created or pwd_last_set >= ak_user.password_change_date:
|
||||
self._logger.debug(
|
||||
"Reset user's password",
|
||||
user=ak_user.username,
|
||||
created=created,
|
||||
pwd_last_set=pwd_last_set,
|
||||
)
|
||||
ak_user.set_unusable_password()
|
||||
ak_user.save()
|
||||
return user_count
|
||||
|
@ -15,9 +15,11 @@ class OAuthSourceForm(forms.ModelForm):
|
||||
self.fields["authentication_flow"].queryset = Flow.objects.filter(
|
||||
designation=FlowDesignation.AUTHENTICATION
|
||||
)
|
||||
self.fields["authentication_flow"].required = True
|
||||
self.fields["enrollment_flow"].queryset = Flow.objects.filter(
|
||||
designation=FlowDesignation.ENROLLMENT
|
||||
)
|
||||
self.fields["enrollment_flow"].required = True
|
||||
if hasattr(self.Meta, "overrides"):
|
||||
for overide_field, overide_value in getattr(self.Meta, "overrides").items():
|
||||
self.fields[overide_field].initial = overide_value
|
||||
|
@ -4,6 +4,7 @@ from typing import Any, Optional
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.http.response import HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
@ -151,6 +152,8 @@ class OAuthCallback(OAuthClientMixin, View):
|
||||
PLAN_CONTEXT_REDIRECT: final_redirect,
|
||||
}
|
||||
)
|
||||
if not flow:
|
||||
return HttpResponseBadRequest()
|
||||
# We run the Flow planner here so we can pass the Pending user in the context
|
||||
planner = FlowPlanner(flow)
|
||||
plan = planner.plan(self.request, kwargs)
|
||||
@ -233,6 +236,9 @@ class OAuthCallback(OAuthClientMixin, View):
|
||||
PLAN_CONTEXT_SOURCES_OAUTH_ACCESS: access,
|
||||
}
|
||||
# We run the Flow planner here so we can pass the Pending user in the context
|
||||
if not source.enrollment_flow:
|
||||
LOGGER.warning("source has no enrollment flow", source=source)
|
||||
return HttpResponseBadRequest()
|
||||
planner = FlowPlanner(source.enrollment_flow)
|
||||
plan = planner.plan(self.request, context)
|
||||
plan.append(in_memory_stage(PostUserEnrollmentStage))
|
||||
|
@ -1,5 +1,8 @@
|
||||
"""AuthenticatorStaticStage API Views"""
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from django_otp.plugins.otp_static.models import StaticDevice
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
||||
@ -19,3 +22,39 @@ class AuthenticatorStaticStageViewSet(ModelViewSet):
|
||||
|
||||
queryset = AuthenticatorStaticStage.objects.all()
|
||||
serializer_class = AuthenticatorStaticStageSerializer
|
||||
|
||||
|
||||
class StaticDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for static authenticator devices"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = StaticDevice
|
||||
fields = ["name", "token_set"]
|
||||
depth = 2
|
||||
|
||||
|
||||
class StaticDeviceViewSet(ModelViewSet):
|
||||
"""Viewset for static authenticator devices"""
|
||||
|
||||
queryset = StaticDevice.objects.none()
|
||||
serializer_class = StaticDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
if not self.request:
|
||||
return super().get_queryset()
|
||||
return StaticDevice.objects.filter(user=self.request.user)
|
||||
|
||||
|
||||
class StaticAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
"""Viewset for static authenticator devices (for admins)"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
queryset = StaticDevice.objects.all()
|
||||
serializer_class = StaticDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
@ -31,7 +31,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
|
||||
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
||||
return AuthenticatorStaticChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-static",
|
||||
"codes": [token.token for token in tokens],
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||
class DisableView(LoginRequiredMixin, View):
|
||||
"""Disable Static Tokens for user"""
|
||||
|
||||
def get(self, request: HttpRequest) -> HttpResponse:
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, **kwargs) -> HttpResponse:
|
||||
"""Delete all the devices for user"""
|
||||
devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
|
||||
devices.delete()
|
||||
|
@ -1,5 +1,8 @@
|
||||
"""AuthenticatorTOTPStage API Views"""
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
||||
@ -19,3 +22,41 @@ class AuthenticatorTOTPStageViewSet(ModelViewSet):
|
||||
|
||||
queryset = AuthenticatorTOTPStage.objects.all()
|
||||
serializer_class = AuthenticatorTOTPStageSerializer
|
||||
|
||||
|
||||
class TOTPDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for totp authenticator devices"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TOTPDevice
|
||||
fields = [
|
||||
"name",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
class TOTPDeviceViewSet(ModelViewSet):
|
||||
"""Viewset for totp authenticator devices"""
|
||||
|
||||
queryset = TOTPDevice.objects.none()
|
||||
serializer_class = TOTPDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
if not self.request:
|
||||
return super().get_queryset()
|
||||
return TOTPDevice.objects.filter(user=self.request.user)
|
||||
|
||||
|
||||
class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
"""Viewset for totp authenticator devices (for admins)"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
queryset = TOTPDevice.objects.all()
|
||||
serializer_class = TOTPDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
@ -51,7 +51,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
|
||||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||
return AuthenticatorTOTPChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-totp",
|
||||
"config_url": device.config_url,
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
"""OTP Validate stage forms"""
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_otp import match_token
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import NotConfiguredAction
|
||||
from authentik.stages.authenticator_validate.models import (
|
||||
AuthenticatorValidateStage,
|
||||
@ -11,35 +8,6 @@ from authentik.stages.authenticator_validate.models import (
|
||||
)
|
||||
|
||||
|
||||
class ValidationForm(forms.Form):
|
||||
"""OTP Validate stage forms"""
|
||||
|
||||
user: User
|
||||
|
||||
code = forms.CharField(
|
||||
label=_("Please enter the token from your device."),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"autocomplete": "one-time-code",
|
||||
"placeholder": "123456",
|
||||
"autofocus": "autofocus",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
|
||||
def clean_code(self):
|
||||
"""Validate code against all confirmed devices"""
|
||||
code = self.cleaned_data.get("code")
|
||||
device = match_token(self.user, code)
|
||||
if not device:
|
||||
raise forms.ValidationError(_("Invalid Token"))
|
||||
return code
|
||||
|
||||
|
||||
class AuthenticatorValidateStageForm(forms.ModelForm):
|
||||
"""OTP Validate stage forms"""
|
||||
|
||||
|
@ -145,7 +145,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
||||
challenges = self.request.session["device_challenges"]
|
||||
return AuthenticatorChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-validate",
|
||||
"device_challenges": challenges,
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
"""AuthenticateWebAuthnStage API Views"""
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
WebAuthnDevice,
|
||||
)
|
||||
|
||||
|
||||
class AuthenticateWebAuthnStageSerializer(StageSerializer):
|
||||
@ -19,3 +24,41 @@ class AuthenticateWebAuthnStageViewSet(ModelViewSet):
|
||||
|
||||
queryset = AuthenticateWebAuthnStage.objects.all()
|
||||
serializer_class = AuthenticateWebAuthnStageSerializer
|
||||
|
||||
|
||||
class WebAuthnDeviceSerializer(ModelSerializer):
|
||||
"""Serializer for WebAuthn authenticator devices"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = WebAuthnDevice
|
||||
fields = [
|
||||
"name",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
class WebAuthnDeviceViewSet(ModelViewSet):
|
||||
"""Viewset for WebAuthn authenticator devices"""
|
||||
|
||||
queryset = WebAuthnDevice.objects.none()
|
||||
serializer_class = WebAuthnDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
if not self.request:
|
||||
return super().get_queryset()
|
||||
return WebAuthnDevice.objects.filter(user=self.request.user)
|
||||
|
||||
|
||||
class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet):
|
||||
"""Viewset for WebAuthn authenticator devices (for admins)"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
queryset = WebAuthnDevice.objects.all()
|
||||
serializer_class = WebAuthnDeviceSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
ordering = ["name"]
|
||||
|
@ -1,7 +1,10 @@
|
||||
"""Webauthn stage forms"""
|
||||
from django import forms
|
||||
|
||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
WebAuthnDevice,
|
||||
)
|
||||
|
||||
|
||||
class AuthenticateWebAuthnStageForm(forms.ModelForm):
|
||||
@ -15,3 +18,16 @@ class AuthenticateWebAuthnStageForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class DeviceEditForm(forms.ModelForm):
|
||||
"""Form to edit webauthn device"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = WebAuthnDevice
|
||||
fields = ["name"]
|
||||
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.1.7 on 2021-03-04 18:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_webauthn", "0003_webauthndevice_confirmed"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="webauthndevice",
|
||||
options={
|
||||
"verbose_name": "WebAuthn Device",
|
||||
"verbose_name_plural": "WebAuthn Devices",
|
||||
},
|
||||
),
|
||||
]
|
@ -79,3 +79,8 @@ class WebAuthnDevice(Device):
|
||||
|
||||
def __str__(self):
|
||||
return self.name or str(self.user)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _("WebAuthn Device")
|
||||
verbose_name_plural = _("WebAuthn Devices")
|
||||
|
@ -122,7 +122,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
||||
|
||||
return AuthenticatorWebAuthnChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-authenticator-webauthn",
|
||||
"registration": make_credential_options.registration_dict,
|
||||
}
|
||||
|
@ -17,6 +17,20 @@
|
||||
Created {{ created_on }}
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<div class="pf-c-data-list__cell">
|
||||
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-update' pk=device.pk %}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
{% trans 'Update' %}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-modal-button href="{% url 'authentik_stages_authenticator_webauthn:device-delete' pk=device.pk %}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
{% trans 'Delete' %}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -1,10 +1,16 @@
|
||||
"""WebAuthn urls"""
|
||||
from django.urls import path
|
||||
|
||||
from authentik.stages.authenticator_webauthn.views import UserSettingsView
|
||||
from authentik.stages.authenticator_webauthn.views import (
|
||||
DeviceDeleteView,
|
||||
DeviceUpdateView,
|
||||
UserSettingsView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"<uuid:stage_uuid>/settings/", UserSettingsView.as_view(), name="user-settings"
|
||||
),
|
||||
path("devices/<int:pk>/delete/", DeviceDeleteView.as_view(), name="device-delete"),
|
||||
path("devices/<int:pk>/update/", DeviceUpdateView.as_view(), name="device-update"),
|
||||
]
|
||||
|
@ -1,8 +1,13 @@
|
||||
"""webauthn views"""
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http.response import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import TemplateView
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import TemplateView, UpdateView
|
||||
|
||||
from authentik.admin.views.utils import DeleteMessageView
|
||||
from authentik.stages.authenticator_webauthn.forms import DeviceEditForm
|
||||
from authentik.stages.authenticator_webauthn.models import (
|
||||
AuthenticateWebAuthnStage,
|
||||
WebAuthnDevice,
|
||||
@ -22,3 +27,34 @@ class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||
)
|
||||
kwargs["stage"] = stage
|
||||
return kwargs
|
||||
|
||||
|
||||
class DeviceUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||
"""Update device"""
|
||||
|
||||
model = WebAuthnDevice
|
||||
form_class = DeviceEditForm
|
||||
template_name = "generic/update.html"
|
||||
success_url = "/"
|
||||
success_message = _("Successfully updated Device")
|
||||
|
||||
def get_object(self) -> WebAuthnDevice:
|
||||
device: WebAuthnDevice = super().get_object()
|
||||
if device.user != self.request.user:
|
||||
raise Http404
|
||||
return device
|
||||
|
||||
|
||||
class DeviceDeleteView(LoginRequiredMixin, DeleteMessageView):
|
||||
"""Delete device"""
|
||||
|
||||
model = WebAuthnDevice
|
||||
template_name = "generic/delete.html"
|
||||
success_url = "/"
|
||||
success_message = _("Successfully deleted Device")
|
||||
|
||||
def get_object(self) -> WebAuthnDevice:
|
||||
device: WebAuthnDevice = super().get_object()
|
||||
if device.user != self.request.user:
|
||||
raise Http404
|
||||
return device
|
||||
|
@ -63,7 +63,7 @@ class CaptchaStageView(ChallengeStageView):
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
return CaptchaChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-captcha",
|
||||
"site_key": self.executor.current_stage.public_key,
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class ConsentStageView(ChallengeStageView):
|
||||
def get_challenge(self) -> Challenge:
|
||||
challenge = ConsentChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-consent",
|
||||
}
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ class DummyStageView(ChallengeStageView):
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
return DummyChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "",
|
||||
"title": self.executor.current_stage.name,
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ class EmailStageView(ChallengeStageView):
|
||||
|
||||
def get_challenge(self) -> Challenge:
|
||||
challenge = EmailChallenge(
|
||||
data={"type": ChallengeTypes.native, "component": "ak-stage-email"}
|
||||
data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
|
||||
)
|
||||
return challenge
|
||||
|
||||
|
@ -78,7 +78,7 @@ class IdentificationStageView(ChallengeStageView):
|
||||
current_stage: IdentificationStage = self.executor.current_stage
|
||||
challenge = IdentificationChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-identification",
|
||||
"primary_action": _("Log in"),
|
||||
"input_type": "text",
|
||||
|
@ -78,7 +78,7 @@ class PasswordStageView(ChallengeStageView):
|
||||
def get_challenge(self) -> Challenge:
|
||||
challenge = PasswordChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-password",
|
||||
}
|
||||
)
|
||||
|
@ -1,9 +1,6 @@
|
||||
{% extends "base/page.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
{% block body %}
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
{% trans 'Reset your password' %}
|
||||
@ -14,4 +11,3 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -164,7 +164,7 @@ class PromptStageView(ChallengeStageView):
|
||||
fields = list(self.executor.current_stage.fields.all().order_by("order"))
|
||||
challenge = PromptChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.native,
|
||||
"type": ChallengeTypes.native.value,
|
||||
"component": "ak-stage-prompt",
|
||||
"fields": [PromptSerializer(field).data for field in fields],
|
||||
},
|
||||
|
@ -279,6 +279,7 @@ stages:
|
||||
displayName: Build static files for e2e
|
||||
inputs:
|
||||
script: |
|
||||
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
|
||||
cd web
|
||||
npm i
|
||||
npm run build
|
||||
@ -378,8 +379,15 @@ stages:
|
||||
python ./scripts/az_do_set_branch.py
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'GHCR'
|
||||
repository: 'beryju/authentik'
|
||||
command: 'buildAndPush'
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/server'
|
||||
command: 'build'
|
||||
Dockerfile: 'Dockerfile'
|
||||
tags: "gh-$(branchName)"
|
||||
tags: 'gh-$(branchName)'
|
||||
arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/server'
|
||||
command: 'push'
|
||||
tags: 'gh-$(branchName)'
|
||||
|
@ -19,7 +19,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
server:
|
||||
image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.1-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.4}
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
@ -27,9 +27,11 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: true
|
||||
volumes:
|
||||
- ./media:/media
|
||||
- ./custom-templates:/templates
|
||||
- geoip:/geoip
|
||||
ports:
|
||||
- 8000
|
||||
networks:
|
||||
@ -45,7 +47,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
worker:
|
||||
image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.1-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.4}
|
||||
command: worker
|
||||
networks:
|
||||
- internal
|
||||
@ -55,14 +57,16 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: true
|
||||
volumes:
|
||||
- ./backups:/backups
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./custom-templates:/templates
|
||||
- geoip:/geoip
|
||||
env_file:
|
||||
- .env
|
||||
static:
|
||||
image: beryju/authentik-static:${AUTHENTIK_TAG:-2021.3.1-rc1}
|
||||
image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.3.4}
|
||||
networks:
|
||||
- internal
|
||||
labels:
|
||||
@ -91,10 +95,21 @@ services:
|
||||
- "127.0.0.1:8080:8080"
|
||||
networks:
|
||||
- internal
|
||||
geoipupdate:
|
||||
image: "maxmindinc/geoipupdate:latest"
|
||||
volumes:
|
||||
- "geoip:/usr/share/GeoIP"
|
||||
environment:
|
||||
GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
|
||||
GEOIPUPDATE_FREQUENCY: "8"
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
database:
|
||||
driver: local
|
||||
geoip:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
internal: {}
|
||||
|
@ -4,7 +4,7 @@ name: authentik
|
||||
home: https://goauthentik.io
|
||||
sources:
|
||||
- https://github.com/BeryJu/authentik
|
||||
version: "2021.3.1-rc1"
|
||||
version: "2021.3.4"
|
||||
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
|
@ -4,7 +4,7 @@
|
||||
|-----------------------------------|-------------------------|-------------|
|
||||
| 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.tag | 2021.3.1-rc1 | Image tag |
|
||||
| image.tag | 2021.3.4 | Image tag |
|
||||
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
|
||||
| serverReplicas | 1 | Replicas for the Server deployment |
|
||||
| workerReplicas | 1 | Replicas for the Worker deployment |
|
||||
@ -22,6 +22,10 @@
|
||||
| config.email.use_ssl | false | Enable SSL |
|
||||
| config.email.timeout | 10 | SMTP Timeout |
|
||||
| config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain |
|
||||
| geoip.enabled | false | Optionally enable GeoIP |
|
||||
| geoip.accountId | | GeoIP MaxMind Account ID |
|
||||
| geoip.licenseKey | | GeoIP MaxMind License key |
|
||||
| geoip.image | maxmindinc/geoipupdate:latest | GeoIP Updater image |
|
||||
| backup.accessKey | | Optionally enable S3 Backup, Access Key |
|
||||
| backup.secretKey | | Optionally enable S3 Backup, Secret Key |
|
||||
| backup.bucket | | Optionally enable S3 Backup, Bucket |
|
||||
|
11
helm/templates/geoip-configmap.yaml
Normal file
11
helm/templates/geoip-configmap.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
{{- if .Values.geoip.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "authentik.fullname" . }}-geoip-config
|
||||
data:
|
||||
GEOIPUPDATE_ACCOUNT_ID: "{{ .Values.geoip.accountId }}"
|
||||
GEOIPUPDATE_LICENSE_KEY: "{{ .Values.geoip.licenseKey }}"
|
||||
GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
|
||||
GEOIPUPDATE_FREQUENCY: "8"
|
||||
{{- end }}
|
39
helm/templates/geoip-deployment.yaml
Normal file
39
helm/templates/geoip-deployment.yaml
Normal file
@ -0,0 +1,39 @@
|
||||
{{- if .Values.geoip.enabled -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "authentik.fullname" . }}-geoip
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
helm.sh/chart: {{ include "authentik.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
k8s.goauthentik.io/component: geoip
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
k8s.goauthentik.io/component: geoip
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
k8s.goauthentik.io/component: geoip
|
||||
spec:
|
||||
containers:
|
||||
- name: geoip
|
||||
image: "{{ .Values.geoip.image }}"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "authentik.fullname" . }}-geoip-config
|
||||
volumeMounts:
|
||||
- name: geoip
|
||||
mountPath: /usr/share/GeoIP
|
||||
volumes:
|
||||
- name: geoip
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "authentik.fullname" . }}-geoip
|
||||
{{- end }}
|
17
helm/templates/geoip-pvc.yaml
Normal file
17
helm/templates/geoip-pvc.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
{{- if .Values.geoip.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "authentik.fullname" . }}-geoip
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
helm.sh/chart: {{ include "authentik.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
{{- end }}
|
121
helm/templates/prom-rules.yaml
Normal file
121
helm/templates/prom-rules.yaml
Normal file
@ -0,0 +1,121 @@
|
||||
{{- if .Values.monitoring.enabled -}}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: {{ include "authentik.fullname" . }}-static-rules
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
helm.sh/chart: {{ include "authentik.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
groups:
|
||||
- name: Aggregate request counters
|
||||
rules:
|
||||
- record: job:django_http_requests_before_middlewares_total:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_before_middlewares_total[30s])) by (job)
|
||||
- record: job:django_http_requests_unknown_latency_total:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_unknown_latency_total[30s])) by (job)
|
||||
- record: job:django_http_ajax_requests_total:sum_rate30s
|
||||
expr: sum(rate(django_http_ajax_requests_total[30s])) by (job)
|
||||
- record: job:django_http_responses_before_middlewares_total:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_before_middlewares_total[30s])) by (job)
|
||||
- record: job:django_http_requests_unknown_latency_including_middlewares_total:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_unknown_latency_including_middlewares_total[30s])) by (job)
|
||||
- record: job:django_http_requests_body_total_bytes:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_body_total_bytes[30s])) by (job)
|
||||
- record: job:django_http_responses_streaming_total:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_streaming_total[30s])) by (job)
|
||||
- record: job:django_http_responses_body_total_bytes:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_body_total_bytes[30s])) by (job)
|
||||
- record: job:django_http_requests_total:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_total_by_method[30s])) by (job)
|
||||
- record: job:django_http_requests_total_by_method:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_total_by_method[30s])) by (job,method)
|
||||
- record: job:django_http_requests_total_by_transport:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_total_by_transport[30s])) by (job,transport)
|
||||
- record: job:django_http_requests_total_by_view:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view)
|
||||
- record: job:django_http_requests_total_by_view_transport_method:sum_rate30s
|
||||
expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view,transport,method)
|
||||
- record: job:django_http_responses_total_by_templatename:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_total_by_templatename[30s])) by (job,templatename)
|
||||
- record: job:django_http_responses_total_by_status:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_total_by_status[30s])) by (job,status)
|
||||
- record: job:django_http_responses_total_by_status_name_method:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_total_by_status_name_method[30s])) by (job,status,name,method)
|
||||
- record: job:django_http_responses_total_by_charset:sum_rate30s
|
||||
expr: sum(rate(django_http_responses_total_by_charset[30s])) by (job,charset)
|
||||
- record: job:django_http_exceptions_total_by_type:sum_rate30s
|
||||
expr: sum(rate(django_http_exceptions_total_by_type[30s])) by (job,type)
|
||||
- record: job:django_http_exceptions_total_by_view:sum_rate30s
|
||||
expr: sum(rate(django_http_exceptions_total_by_view[30s])) by (job,view)
|
||||
- name: Aggregate latency histograms
|
||||
rules:
|
||||
- record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "50"
|
||||
- record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "95"
|
||||
- record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "99"
|
||||
- record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "99.9"
|
||||
- record: job:django_http_requests_latency_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "50"
|
||||
- record: job:django_http_requests_latency_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "95"
|
||||
- record: job:django_http_requests_latency_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "99"
|
||||
- record: job:django_http_requests_latency_seconds:quantile_rate30s
|
||||
expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
|
||||
labels:
|
||||
quantile: "99.9"
|
||||
- name: Aggregate model operations
|
||||
rules:
|
||||
- record: job:django_model_inserts_total:sum_rate1m
|
||||
expr: sum(rate(django_model_inserts_total[1m])) by (job, model)
|
||||
- record: job:django_model_updates_total:sum_rate1m
|
||||
expr: sum(rate(django_model_updates_total[1m])) by (job, model)
|
||||
- record: job:django_model_deletes_total:sum_rate1m
|
||||
expr: sum(rate(django_model_deletes_total[1m])) by (job, model)
|
||||
- name: Aggregate database operations
|
||||
rules:
|
||||
- record: job:django_db_new_connections_total:sum_rate30s
|
||||
expr: sum(rate(django_db_new_connections_total[30s])) by (alias, vendor)
|
||||
- record: job:django_db_new_connection_errors_total:sum_rate30s
|
||||
expr: sum(rate(django_db_new_connection_errors_total[30s])) by (alias, vendor)
|
||||
- record: job:django_db_execute_total:sum_rate30s
|
||||
expr: sum(rate(django_db_execute_total[30s])) by (alias, vendor)
|
||||
- record: job:django_db_execute_many_total:sum_rate30s
|
||||
expr: sum(rate(django_db_execute_many_total[30s])) by (alias, vendor)
|
||||
- record: job:django_db_errors_total:sum_rate30s
|
||||
expr: sum(rate(django_db_errors_total[30s])) by (alias, vendor, type)
|
||||
- name: Aggregate migrations
|
||||
rules:
|
||||
- record: job:django_migrations_applied_total:max
|
||||
expr: max(django_migrations_applied_total) by (job, connection)
|
||||
- record: job:django_migrations_unapplied_total:max
|
||||
expr: max(django_migrations_unapplied_total) by (job, connection)
|
||||
- name: Alerts
|
||||
rules:
|
||||
- alert: UnappliedMigrations
|
||||
expr: job:django_migrations_unapplied_total:max > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: testing
|
||||
{{- end }}
|
17
helm/templates/static-sm.yaml
Normal file
17
helm/templates/static-sm.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
{{- if .Values.monitoring.enabled -}}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
helm.sh/chart: {{ include "authentik.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
name: {{ include "authentik.fullname" . }}-static-monitoring
|
||||
spec:
|
||||
endpoints:
|
||||
- port: http
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s.goauthentik.io/component: static
|
||||
{{- end }}
|
@ -88,9 +88,17 @@ spec:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: "postgresql-password"
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
- name: AUTHENTIK_AUTHENTIK__GEOIP
|
||||
value: /geoip/GeoLite2-City.mmdb
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: authentik-uploads
|
||||
mountPath: /media
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
- name: geoip
|
||||
mountPath: /geoip
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
@ -116,3 +124,8 @@ spec:
|
||||
- name: authentik-uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "authentik.fullname" . }}-uploads
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
- name: geoip
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "authentik.fullname" . }}-geoip
|
||||
{{- end }}
|
||||
|
26
helm/templates/web-sm.yaml
Normal file
26
helm/templates/web-sm.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
{{- if .Values.monitoring.enabled -}}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "authentik.name" . }}
|
||||
helm.sh/chart: {{ include "authentik.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
name: {{ include "authentik.fullname" . }}-web-monitoring
|
||||
spec:
|
||||
endpoints:
|
||||
- basicAuth:
|
||||
password:
|
||||
name: {{ include "authentik.fullname" . }}-secret-key
|
||||
key: SECRET_KEY
|
||||
username:
|
||||
name: {{ include "authentik.fullname" . }}-secret-key
|
||||
key: monitoring_username
|
||||
port: http
|
||||
path: /metrics/
|
||||
interval: 10s
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s.goauthentik.io/component: web
|
||||
{{- end }}
|
@ -68,6 +68,15 @@ spec:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: "postgresql-password"
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
- name: AUTHENTIK_AUTHENTIK__GEOIP
|
||||
value: /geoip/GeoLite2-City.mmdb
|
||||
{{- end }}
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
volumeMounts:
|
||||
- name: geoip
|
||||
mountPath: /geoip
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 150m
|
||||
@ -75,3 +84,9 @@ spec:
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 600M
|
||||
{{ if .Values.geoip.enabled -}}
|
||||
volumes:
|
||||
- name: geoip
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "authentik.fullname" . }}-geoip
|
||||
{{- end -}}
|
||||
|
@ -1,22 +0,0 @@
|
||||
image:
|
||||
tag: gh-master
|
||||
pullPolicy: Always
|
||||
|
||||
serverReplicas: 1
|
||||
workerReplicas: 1
|
||||
|
||||
config:
|
||||
# Log level used by web and worker
|
||||
# Can be either debug, info, warning, error
|
||||
logLevel: debug
|
||||
|
||||
ingress:
|
||||
hosts:
|
||||
- authentik.127.0.0.1.nip.io
|
||||
|
||||
# These values influence the bundled postgresql and redis charts, but are also used by authentik to connect
|
||||
postgresql:
|
||||
postgresqlPassword: EK-5jnKfjrGRm<77
|
||||
|
||||
redis:
|
||||
password: password
|
@ -5,7 +5,7 @@ image:
|
||||
name: beryju/authentik
|
||||
name_static: beryju/authentik-static
|
||||
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
|
||||
tag: 2021.3.1-rc1
|
||||
tag: 2021.3.4
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
serverReplicas: 1
|
||||
@ -14,6 +14,9 @@ workerReplicas: 1
|
||||
# Enable the Kubernetes integration which lets authentik deploy outposts into kubernetes
|
||||
kubernetesIntegration: true
|
||||
|
||||
monitoring:
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# Optionally specify fixed secret_key, otherwise generated automatically
|
||||
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
||||
@ -41,6 +44,13 @@ config:
|
||||
# Email address authentik will send from, should have a correct @domain
|
||||
from: authentik@localhost
|
||||
|
||||
# Enable MaxMind GeoIP
|
||||
geoip:
|
||||
enabled: false
|
||||
accountId: ""
|
||||
licenseKey: ""
|
||||
image: maxmindinc/geoipupdate:latest
|
||||
|
||||
# Enable Database Backups to S3
|
||||
# backup:
|
||||
# accessKey: access-key
|
||||
|
@ -33,7 +33,7 @@ stages:
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: 'outpost/pkg/'
|
||||
artifact: 'swagger_client'
|
||||
artifact: 'go_swagger_client'
|
||||
publishLocation: 'pipeline'
|
||||
- stage: lint
|
||||
jobs:
|
||||
@ -51,7 +51,7 @@ stages:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
@ -70,7 +70,7 @@ stages:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: Go@0
|
||||
inputs:
|
||||
@ -89,7 +89,7 @@ stages:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: 'swagger_client'
|
||||
artifactName: 'go_swagger_client'
|
||||
path: "outpost/pkg/"
|
||||
- task: Bash@3
|
||||
inputs:
|
||||
@ -98,8 +98,8 @@ stages:
|
||||
python ./scripts/az_do_set_branch.py
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'GHCR'
|
||||
repository: 'beryju/authentik-proxy'
|
||||
containerRegistry: 'beryjuorg-harbor'
|
||||
repository: 'authentik/proxy'
|
||||
command: 'buildAndPush'
|
||||
Dockerfile: 'outpost/proxy.Dockerfile'
|
||||
buildContext: 'outpost/'
|
||||
|
@ -24,7 +24,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
|
||||
github.com/recws-org/recws v1.2.1
|
||||
github.com/sirupsen/logrus v1.8.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/afero v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
@ -618,6 +618,8 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
|
||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
|
@ -1,3 +1,3 @@
|
||||
package pkg
|
||||
|
||||
const VERSION = "2021.3.1-rc1"
|
||||
const VERSION = "2021.3.4"
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.16.0 AS builder
|
||||
FROM golang:1.16.2 AS builder
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
3587
swagger.yaml
3587
swagger.yaml
File diff suppressed because it is too large
Load Diff
@ -93,7 +93,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||
|
||||
self.initial_stages()
|
||||
|
||||
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
interface_admin = self.get_shadow_root("ak-interface-admin")
|
||||
wait = WebDriverWait(interface_admin, self.wait_timeout)
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
self.driver.get(self.shell_url("authentik_core:user-settings"))
|
||||
|
||||
user = User.objects.get(username="foo")
|
||||
@ -188,7 +191,11 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||
self.driver.switch_to.window(self.driver.window_handles[0])
|
||||
|
||||
# We're now logged in
|
||||
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
wait = WebDriverWait(
|
||||
self.get_shadow_root("ak-interface-admin"), self.wait_timeout
|
||||
)
|
||||
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
|
||||
self.driver.get(self.shell_url("authentik_core:user-settings"))
|
||||
|
||||
self.assert_user(User.objects.get(username="foo"))
|
||||
|
273
tests/e2e/test_provider_oauth2_oidc_implicit.py
Normal file
273
tests/e2e/test_provider_oauth2_oidc_implicit.py
Normal file
@ -0,0 +1,273 @@
|
||||
"""test OAuth2 OpenID Provider flow"""
|
||||
from json import loads
|
||||
from sys import platform
|
||||
from time import sleep
|
||||
from unittest.case import skipUnless
|
||||
|
||||
from docker import DockerClient, from_env
|
||||
from docker.models.containers import Container
|
||||
from docker.types import Healthcheck
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.policies.expression.models import ExpressionPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.oauth2.constants import (
|
||||
SCOPE_OPENID,
|
||||
SCOPE_OPENID_EMAIL,
|
||||
SCOPE_OPENID_PROFILE,
|
||||
)
|
||||
from authentik.providers.oauth2.generators import (
|
||||
generate_client_id,
|
||||
generate_client_secret,
|
||||
)
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||
from tests.e2e.utils import (
|
||||
USER,
|
||||
SeleniumTestCase,
|
||||
apply_migration,
|
||||
object_manager,
|
||||
retry,
|
||||
)
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||
"""test OAuth with OpenID Provider flow"""
|
||||
|
||||
def setUp(self):
|
||||
self.client_id = generate_client_id()
|
||||
self.client_secret = generate_client_secret()
|
||||
self.application_slug = "test"
|
||||
super().setUp()
|
||||
|
||||
def setup_client(self) -> Container:
|
||||
"""Setup client saml-sp container which we test SAML against"""
|
||||
sleep(1)
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image="beryju/oidc-test-client",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
healthcheck=Healthcheck(
|
||||
test=["CMD", "wget", "--spider", "http://localhost:9009/health"],
|
||||
interval=5 * 100 * 1000000,
|
||||
start_period=1 * 100 * 1000000,
|
||||
),
|
||||
environment={
|
||||
"OIDC_CLIENT_ID": self.client_id,
|
||||
"OIDC_CLIENT_SECRET": self.client_secret,
|
||||
"OIDC_PROVIDER": f"{self.live_server_url}/application/o/{self.application_slug}/",
|
||||
},
|
||||
)
|
||||
while True:
|
||||
container.reload()
|
||||
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
||||
if status == "healthy":
|
||||
return container
|
||||
LOGGER.info("Container failed healthcheck")
|
||||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
def test_redirect_uri_error(self):
|
||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||
sleep(1)
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=self.application_slug,
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
redirect_uris="http://localhost:9009/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
|
||||
)
|
||||
)
|
||||
provider.save()
|
||||
Application.objects.create(
|
||||
name=self.application_slug,
|
||||
slug=self.application_slug,
|
||||
provider=provider,
|
||||
)
|
||||
self.container = self.setup_client()
|
||||
|
||||
self.driver.get("http://localhost:9009/implicit/")
|
||||
sleep(2)
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CLASS_NAME, "pf-c-title").text,
|
||||
"Redirect URI Error",
|
||||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
def test_authorization_consent_implied(self):
|
||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||
sleep(1)
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=self.application_slug,
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
|
||||
)
|
||||
)
|
||||
provider.save()
|
||||
Application.objects.create(
|
||||
name=self.application_slug,
|
||||
slug=self.application_slug,
|
||||
provider=provider,
|
||||
)
|
||||
self.container = self.setup_client()
|
||||
|
||||
self.driver.get("http://localhost:9009/implicit/")
|
||||
self.login()
|
||||
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre")))
|
||||
sleep(1)
|
||||
body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
|
||||
print(body)
|
||||
self.assertEqual(body["profile"]["nickname"], USER().username)
|
||||
self.assertEqual(body["profile"]["name"], USER().name)
|
||||
self.assertEqual(body["profile"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
def test_authorization_consent_explicit(self):
|
||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||
sleep(1)
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-explicit-consent"
|
||||
)
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=self.application_slug,
|
||||
authorization_flow=authorization_flow,
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
|
||||
)
|
||||
)
|
||||
provider.save()
|
||||
app = Application.objects.create(
|
||||
name=self.application_slug,
|
||||
slug=self.application_slug,
|
||||
provider=provider,
|
||||
)
|
||||
self.container = self.setup_client()
|
||||
|
||||
self.driver.get("http://localhost:9009/implicit/")
|
||||
self.login()
|
||||
|
||||
self.wait.until(
|
||||
ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor"))
|
||||
)
|
||||
|
||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
consent_stage = self.get_shadow_root("ak-stage-consent", flow_executor)
|
||||
|
||||
self.assertIn(
|
||||
app.name,
|
||||
consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text,
|
||||
)
|
||||
consent_stage.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
("[type=submit]"),
|
||||
).click()
|
||||
|
||||
self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre")))
|
||||
sleep(1)
|
||||
body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
|
||||
|
||||
self.assertEqual(body["profile"]["nickname"], USER().username)
|
||||
self.assertEqual(body["profile"]["name"], USER().name)
|
||||
self.assertEqual(body["profile"]["email"], USER().email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_core", "0003_default_user")
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
def test_authorization_denied(self):
|
||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||
sleep(1)
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-explicit-consent"
|
||||
)
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=self.application_slug,
|
||||
authorization_flow=authorization_flow,
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=CertificateKeyPair.objects.first(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
scope_name__in=[SCOPE_OPENID, SCOPE_OPENID_EMAIL, SCOPE_OPENID_PROFILE]
|
||||
)
|
||||
)
|
||||
provider.save()
|
||||
app = Application.objects.create(
|
||||
name=self.application_slug,
|
||||
slug=self.application_slug,
|
||||
provider=provider,
|
||||
)
|
||||
|
||||
negative_policy = ExpressionPolicy.objects.create(
|
||||
name="negative-static", expression="return False"
|
||||
)
|
||||
PolicyBinding.objects.create(target=app, policy=negative_policy, order=0)
|
||||
|
||||
self.container = self.setup_client()
|
||||
self.driver.get("http://localhost:9009/implicit/")
|
||||
self.login()
|
||||
self.wait.until(
|
||||
ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))
|
||||
)
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CSS_SELECTOR, "header > h1").text,
|
||||
"Permission denied",
|
||||
)
|
@ -3,11 +3,13 @@ from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
from time import sleep
|
||||
|
||||
import yaml
|
||||
from django.test import TestCase
|
||||
from docker import DockerClient, from_env
|
||||
from docker.models.containers import Container
|
||||
from docker.types.healthcheck import Healthcheck
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
@ -93,3 +95,14 @@ class OutpostDockerTests(TestCase):
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
controller.up()
|
||||
controller.down()
|
||||
|
||||
def test_docker_static(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
manifest = controller.get_static_deployment()
|
||||
compose = yaml.load(manifest, Loader=yaml.SafeLoader)
|
||||
self.assertEqual(compose["version"], "3.5")
|
||||
self.assertEqual(
|
||||
compose["services"]["authentik_proxy"]["image"],
|
||||
f"beryju/authentik-proxy:{__version__}",
|
||||
)
|
||||
|
108
tests/integration/test_proxy_docker.py
Normal file
108
tests/integration/test_proxy_docker.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""outpost tests"""
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
from time import sleep
|
||||
|
||||
import yaml
|
||||
from django.test import TestCase
|
||||
from docker import DockerClient, from_env
|
||||
from docker.models.containers import Container
|
||||
from docker.types.healthcheck import Healthcheck
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
||||
from authentik.providers.proxy.controllers.docker import DockerController
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
class TestProxyDocker(TestCase):
|
||||
"""Test Docker Controllers"""
|
||||
|
||||
def _start_container(self, ssl_folder: str) -> Container:
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image="library/docker:dind",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
remove=True,
|
||||
privileged=True,
|
||||
healthcheck=Healthcheck(
|
||||
test=["CMD", "docker", "info"],
|
||||
interval=5 * 100 * 1000000,
|
||||
start_period=5 * 100 * 1000000,
|
||||
),
|
||||
environment={"DOCKER_TLS_CERTDIR": "/ssl"},
|
||||
volumes={
|
||||
f"{ssl_folder}/": {
|
||||
"bind": "/ssl",
|
||||
}
|
||||
},
|
||||
)
|
||||
while True:
|
||||
container.reload()
|
||||
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
||||
if status == "healthy":
|
||||
return container
|
||||
sleep(1)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.ssl_folder = mkdtemp()
|
||||
self.container = self._start_container(self.ssl_folder)
|
||||
# Ensure that local connection have been created
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
external_host="http://localhost",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
)
|
||||
authentication_kp = CertificateKeyPair.objects.create(
|
||||
name="docker-authentication",
|
||||
certificate_data=open(f"{self.ssl_folder}/client/cert.pem").read(),
|
||||
key_data=open(f"{self.ssl_folder}/client/key.pem").read(),
|
||||
)
|
||||
verification_kp = CertificateKeyPair.objects.create(
|
||||
name="docker-verification",
|
||||
certificate_data=open(f"{self.ssl_folder}/client/ca.pem").read(),
|
||||
)
|
||||
self.service_connection = DockerServiceConnection.objects.create(
|
||||
url="https://localhost:2376",
|
||||
tls_verification=verification_kp,
|
||||
tls_authentication=authentication_kp,
|
||||
)
|
||||
self.outpost: Outpost = Outpost.objects.create(
|
||||
name="test",
|
||||
type=OutpostType.PROXY,
|
||||
service_connection=self.service_connection,
|
||||
)
|
||||
self.outpost.providers.add(self.provider)
|
||||
self.outpost.save()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
self.container.kill()
|
||||
try:
|
||||
rmtree(self.ssl_folder)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
def test_docker_controller(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
controller.up()
|
||||
controller.down()
|
||||
|
||||
def test_docker_static(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
manifest = controller.get_static_deployment()
|
||||
compose = yaml.load(manifest, Loader=yaml.SafeLoader)
|
||||
self.assertEqual(compose["version"], "3.5")
|
||||
self.assertEqual(
|
||||
compose["services"]["authentik_proxy"]["image"],
|
||||
f"beryju/authentik-proxy:{__version__}",
|
||||
)
|
@ -9,7 +9,7 @@ from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesCont
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
class TestControllers(TestCase):
|
||||
class TestProxyKubernetes(TestCase):
|
||||
"""Test Controllers"""
|
||||
|
||||
def setUp(self):
|
||||
|
@ -4,3 +4,8 @@ node_modules
|
||||
dist
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
# don't lint generated code
|
||||
src/api/apis
|
||||
src/api/models
|
||||
src/api/index.ts
|
||||
src/api/runtime.ts
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user