Merge branch 'master' into version-2021.3

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

# Conflicts:
#	docker-compose.yml
#	helm/README.md
#	web/src/authentik.css
#	web/src/flows/FlowExecutor.ts
#	web/src/flows/stages/identification/IdentificationStage.ts
#	website/docs/installation/kubernetes.md
This commit is contained in:
Jens Langhammer 2021-03-13 21:36:17 +01:00
commit 2713b05e8c
198 changed files with 38403 additions and 4958 deletions

View File

@ -36,3 +36,7 @@ values =
[bumpversion:file:outpost/pkg/version.go] [bumpversion:file:outpost/pkg/version.go]
[bumpversion:file:web/src/constants.ts] [bumpversion:file:web/src/constants.ts]
[bumpversion:file:website/docs/outpusts/manual-deploy-docker-compose.md]
[bumpversion:file:website/docs/outpusts/manual-deploy-kubernetes.md]

View File

@ -59,6 +59,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - 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 - name: Docker Login Registry
env: env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -45,4 +45,5 @@ COPY ./lifecycle/ /lifecycle
USER authentik USER authentik
STOPSIGNAL SIGINT STOPSIGNAL SIGINT
ENV TMPDIR /dev/shm/ ENV TMPDIR /dev/shm/
ENV PYTHONUBUFFERED 1
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]

174
Pipfile.lock generated
View File

@ -18,45 +18,45 @@
"default": { "default": {
"aiohttp": { "aiohttp": {
"hashes": [ "hashes": [
"sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0", "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe",
"sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6", "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe",
"sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf", "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5",
"sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9", "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8",
"sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e", "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd",
"sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0", "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb",
"sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329", "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c",
"sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2", "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87",
"sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40", "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0",
"sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a", "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290",
"sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4", "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5",
"sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de", "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287",
"sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9", "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde",
"sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9", "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf",
"sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb", "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8",
"sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076", "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16",
"sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de", "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf",
"sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907", "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809",
"sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d", "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213",
"sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536", "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f",
"sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d", "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013",
"sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54", "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b",
"sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc", "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9",
"sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212", "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5",
"sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9", "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb",
"sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d", "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df",
"sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b", "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4",
"sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7", "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439",
"sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81", "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f",
"sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c", "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22",
"sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895", "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f",
"sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297", "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5",
"sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb", "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970",
"sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe", "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009",
"sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242", "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc",
"sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0", "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
"sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2" "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
], ],
"version": "==3.7.4" "version": "==3.7.4.post0"
}, },
"aioredis": { "aioredis": {
"hashes": [ "hashes": [
@ -116,18 +116,17 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:2219f1ebe88d266afa5516f993983eba8742b957fa4fd6854f3c73aa3030e931", "sha256:64a8900b3a110e2d6ff4d87f4d8cd56f0c8527361d9fc9385fcb50efe7a4975a",
"sha256:c0d51f344b71656c2d395d2168600d91bea252a64fb5d503a955ea96426cde8b" "sha256:8e9ff8006c41889ed8a11831dee62adf922e071f14d54c52946d1f7855ae7a8e"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.20" "version": "==1.17.26"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:80c32a81fb1ee8bdfa074a79bfb885bb2006e8a9782f2353c0c9f6392704e13a", "sha256:4a785847a351e59f2329627fc9a19cf50f07644ea68996a1595d5a20487a423f"
"sha256:e9e724b59278ebf5caf032be1e32bde0990d79e8052e3bbbb97b6c1d32feba28"
], ],
"version": "==1.20.20" "version": "==1.20.26"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -217,10 +216,10 @@
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"version": "==3.0.4" "version": "==4.0.0"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -304,11 +303,11 @@
}, },
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.6.0" "version": "==0.7.1"
}, },
"django": { "django": {
"hashes": [ "hashes": [
@ -445,10 +444,10 @@
}, },
"google-auth": { "google-auth": {
"hashes": [ "hashes": [
"sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65", "sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f",
"sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e" "sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"
], ],
"version": "==1.27.0" "version": "==1.27.1"
}, },
"gunicorn": { "gunicorn": {
"hashes": [ "hashes": [
@ -817,10 +816,10 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974", "sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a",
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd" "sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"
], ],
"version": "==3.0.16" "version": "==3.0.17"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -1024,15 +1023,23 @@
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc" "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.4.1" "version": "==5.4.1"
@ -1069,10 +1076,47 @@
}, },
"ruamel.yaml": { "ruamel.yaml": {
"hashes": [ "hashes": [
"sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5", "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3",
"sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e" "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": { "s3transfer": {
"hashes": [ "hashes": [
@ -1765,15 +1809,23 @@
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc" "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.4.1" "version": "==5.4.1"

View File

@ -7,8 +7,8 @@ from django.db.models import Count, ExpressionWrapper, F, Model
from django.db.models.fields import DurationField from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour from django.db.models.functions import ExtractHour
from django.utils.timezone import now from django.utils.timezone import now
from drf_yasg2.utils import swagger_auto_schema from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
from rest_framework.fields import SerializerMethodField from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response 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): for hour in range(0, -24, -1):
results.append( results.append(
{ {
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000, "x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
"y": data[hour * -1], * 1000,
"y_cord": data[hour * -1],
} }
) )
return results 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""" """Login Metrics per 1h"""
logins_per_1h = SerializerMethodField() logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField() logins_failed_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_per_1h(self, _): def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours""" """Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN) return get_events_per_1h(action=EventAction.LOGIN)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _): def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED) return get_events_per_1h(action=EventAction.LOGIN_FAILED)
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
permission_classes = [IsAdminUser] 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: def list(self, request: Request) -> Response:
"""Login Metrics per 1h""" """Login Metrics per 1h"""
serializer = AdministrationMetricsSerializer(True) serializer = LoginMetricsSerializer(True)
return Response(serializer.data) return Response(serializer.data)

View File

@ -25,8 +25,8 @@ class TaskSerializer(Serializer):
task_finish_timestamp = DateTimeField(source="finish_timestamp") task_finish_timestamp = DateTimeField(source="finish_timestamp")
status = ChoiceField( status = ChoiceField(
source="result.status.value", source="result.status.name",
choices=[(x.value, x.name) for x in TaskResultStatus], choices=[(x.name, x.name) for x in TaskResultStatus],
) )
messages = ListField(source="result.messages") messages = ListField(source="result.messages")

View File

@ -1,4 +1,5 @@
"""api v2 urls""" """api v2 urls"""
from django.conf import settings
from django.urls import path, re_path from django.urls import path, re_path
from drf_yasg2 import openapi from drf_yasg2 import openapi
from drf_yasg2.views import get_schema_view from drf_yasg2.views import get_schema_view
@ -54,12 +55,24 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
from authentik.sources.oauth.api import OAuthSourceViewSet from authentik.sources.oauth.api import OAuthSourceViewSet
from authentik.sources.saml.api import SAMLSourceViewSet from authentik.sources.saml.api import SAMLSourceViewSet
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet from authentik.stages.authenticator_static.api import (
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
StaticDeviceViewSet,
)
from authentik.stages.authenticator_totp.api import (
AuthenticatorTOTPStageViewSet,
TOTPAdminDeviceViewSet,
TOTPDeviceViewSet,
)
from authentik.stages.authenticator_validate.api import ( from authentik.stages.authenticator_validate.api import (
AuthenticatorValidateStageViewSet, 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.captcha.api import CaptchaStageViewSet
from authentik.stages.consent.api import ConsentStageViewSet from authentik.stages.consent.api import ConsentStageViewSet
from authentik.stages.deny.api import DenyStageViewSet from authentik.stages.deny.api import DenyStageViewSet
@ -133,6 +146,13 @@ router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
router.register("propertymappings/saml", SAMLPropertyMappingViewSet) router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register("propertymappings/scope", ScopeMappingViewSet) 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/all", StageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet) router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet) router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
@ -164,27 +184,26 @@ info = openapi.Info(
name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE" name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
), ),
) )
SchemaView = get_schema_view( SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
info,
public=True,
permission_classes=(AllowAny,),
)
urlpatterns = [ urlpatterns = router.urls + [
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"),
path( path(
"flows/executor/<slug:flow_slug>/", "flows/executor/<slug:flow_slug>/",
FlowExecutorView.as_view(), FlowExecutorView.as_view(),
name="flow-executor", 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",
),
]

View File

@ -2,6 +2,7 @@
from django.core.cache import cache from django.core.cache import cache
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http.response import Http404 from django.http.response import Http404
from drf_yasg2.utils import swagger_auto_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
@ -13,7 +14,7 @@ from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.admin.api.metrics import 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.api.providers import ProviderSerializer
from authentik.core.models import Application from authentik.core.models import Application
from authentik.events.models import EventAction from authentik.events.models import EventAction
@ -109,6 +110,7 @@ class ApplicationViewSet(ModelViewSet):
serializer = self.get_serializer(allowed_applications, many=True) serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data) return self.get_paginated_response(serializer.data)
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
@action(detail=True) @action(detail=True)
def metrics(self, request: Request, slug: str): def metrics(self, request: Request, slug: str):
"""Metrics for application logins""" """Metrics for application logins"""

View File

@ -21,3 +21,4 @@ class GroupViewSet(ModelViewSet):
serializer_class = GroupSerializer serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"] search_fields = ["name", "is_superuser"]
filterset_fields = ["name", "is_superuser"] filterset_fields = ["name", "is_superuser"]
ordering = ["name"]

View File

@ -28,9 +28,9 @@ class MetaNameSerializer(Serializer):
class TypeCreateSerializer(Serializer): class TypeCreateSerializer(Serializer):
"""Types of an object that can be created""" """Types of an object that can be created"""
name = CharField(read_only=True) name = CharField(required=True)
description = CharField(read_only=True) description = CharField(required=True)
link = CharField(read_only=True) link = CharField(required=True)
def create(self, validated_data: dict) -> Model: def create(self, validated_data: dict) -> Model:
raise NotImplementedError raise NotImplementedError

View File

@ -1,9 +1,6 @@
{% extends container_template|default:"administration/base.html" %}
{% load i18n %} {% load i18n %}
{% load authentik_utils %} {% load authentik_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light"> <section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content"> <div class="pf-c-content">
{% block above_form %} {% block above_form %}
@ -38,4 +35,3 @@
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" /> <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> <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
</footer> </footer>
{% endblock %}

View File

@ -7,6 +7,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
@ -34,7 +35,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
form_class = UserDetailForm form_class = UserDetailForm
success_message = _("Successfully updated user.") success_message = _("Successfully updated user.")
success_url = "/" success_url = reverse_lazy("authentik_core:user-details")
def get_object(self): def get_object(self):
return self.request.user return self.request.user

View File

@ -13,7 +13,7 @@ class NotificationSerializer(ModelSerializer):
body = ReadOnlyField() body = ReadOnlyField()
severity = ReadOnlyField() severity = ReadOnlyField()
event = EventSerializer() event = EventSerializer(required=False)
class Meta: class Meta:

View File

@ -1,11 +1,12 @@
"""NotificationTransport API Views""" """NotificationTransport API Views"""
from django.http.response import Http404 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 guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action 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.request import Request
from rest_framework.response import Response 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 rest_framework.viewsets import ModelViewSet
from authentik.events.models import ( 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): class NotificationTransportViewSet(ModelViewSet):
"""NotificationTransport Viewset""" """NotificationTransport Viewset"""
queryset = NotificationTransport.objects.all() queryset = NotificationTransport.objects.all()
serializer_class = NotificationTransportSerializer serializer_class = NotificationTransportSerializer
@swagger_auto_schema(
responses={200: NotificationTransportTestSerializer(many=False)},
request_body=no_body,
)
@action(detail=True, methods=["post"]) @action(detail=True, methods=["post"])
# pylint: disable=invalid-name # pylint: disable=invalid-name
def test(self, request: Request, pk=None) -> Response: def test(self, request: Request, pk=None) -> Response:
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
user=request.user, user=request.user,
) )
try: try:
return Response(transport.send(notification)) response = NotificationTransportTestSerializer(
data={"messages": transport.send(notification)}
)
response.is_valid()
return Response(response.data)
except NotificationTransportError as exc: except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=503) return Response(str(exc.__cause__ or None), status=503)

View File

@ -12,7 +12,10 @@ def get_geoip_reader() -> Optional[Reader]:
path = CONFIG.y("authentik.geoip") path = CONFIG.y("authentik.geoip")
if path == "" or not path: if path == "" or not path:
return None return None
return Reader(path) try:
return Reader(path)
except OSError:
return None
GEOIP_READER = get_geoip_reader() GEOIP_READER = get_geoip_reader()

View File

@ -38,7 +38,9 @@ class Challenge(Serializer):
"""Challenge that gets sent to the client based on which stage """Challenge that gets sent to the client based on which stage
is currently active""" is currently active"""
type = ChoiceField(choices=list(ChallengeTypes)) type = ChoiceField(
choices=[(x.name, x.name) for x in ChallengeTypes],
)
component = CharField(required=False) component = CharField(required=False)
title = CharField(required=False) title = CharField(required=False)
@ -90,7 +92,7 @@ class ChallengeResponse(Serializer):
stage: Optional["StageView"] stage: Optional["StageView"]
def __init__(self, instance, data, **kwargs): def __init__(self, instance=None, data=None, **kwargs):
self.stage = kwargs.pop("stage", None) self.stage = kwargs.pop("stage", None)
super().__init__(instance=instance, data=data, **kwargs) super().__init__(instance=instance, data=data, **kwargs)

View File

@ -4,6 +4,7 @@ from django.http import HttpRequest
from django.http.request import QueryDict from django.http.request import QueryDict
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.views.generic.base import View from django.views.generic.base import View
from rest_framework.request import Request
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.models import DEFAULT_AVATAR, User from authentik.core.models import DEFAULT_AVATAR, User
@ -67,9 +68,9 @@ class ChallengeStageView(StageView):
return HttpChallengeResponse(challenge) return HttpChallengeResponse(challenge)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def post(self, request: Request, *args, **kwargs) -> HttpResponse:
"""Handle challenge response""" """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(): if not challenge.is_valid():
return self.challenge_invalid(challenge) return self.challenge_invalid(challenge)
return self.challenge_valid(challenge) return self.challenge_valid(challenge)

View File

@ -9,11 +9,16 @@ from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import TemplateView, View 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 structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict from authentik.events.models import cleanse_dict
from authentik.flows.challenge import ( from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
ChallengeTypes, ChallengeTypes,
HttpChallengeResponse, HttpChallengeResponse,
RedirectChallenge, RedirectChallenge,
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
@method_decorator(xframe_options_sameorigin, name="dispatch") @method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(View): class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views""" """Stage 1 Flow executor, passing requests to Stage Views"""
permission_classes = [AllowAny]
flow: Flow flow: Flow
plan: Optional[FlowPlan] = None plan: Optional[FlowPlan] = None
@ -113,8 +120,13 @@ class FlowExecutorView(View):
self.current_stage_view.request = request self.current_stage_view.request = request
return super().dispatch(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: 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( self._logger.debug(
"f(exec): Passing GET", "f(exec): Passing GET",
view_class=class_to_path(self.current_stage_view.__class__), view_class=class_to_path(self.current_stage_view.__class__),
@ -127,8 +139,13 @@ class FlowExecutorView(View):
self._logger.exception(exc) self._logger.exception(exc)
return to_stage_response(request, FlowErrorResponse(request, 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: 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( self._logger.debug(
"f(exec): Passing POST", "f(exec): Passing POST",
view_class=class_to_path(self.current_stage_view.__class__), view_class=class_to_path(self.current_stage_view.__class__),
@ -175,8 +192,10 @@ class FlowExecutorView(View):
"f(exec): Continuing with next stage", "f(exec): Continuing with next stage",
reamining=len(self.plan.stages), reamining=len(self.plan.stages),
) )
kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug})
return redirect_with_qs( 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 # User passed all stages
self._logger.debug( self._logger.debug(

View File

@ -13,6 +13,7 @@ redis:
ws_db: 2 ws_db: 2
debug: false debug: false
log_level: info log_level: info
# Error reporting, sends stacktrace to sentry.beryju.org # Error reporting, sends stacktrace to sentry.beryju.org

View File

@ -55,13 +55,21 @@ class OutpostConsumer(AuthJsonConsumer):
OutpostState( OutpostState(
uid=self.channel_name, last_seen=datetime.now(), _outpost=self.outpost uid=self.channel_name, last_seen=datetime.now(), _outpost=self.outpost
).save(timeout=OUTPOST_HELLO_INTERVAL * 1.5) ).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 # pylint: disable=unused-argument
def disconnect(self, close_code): def disconnect(self, close_code):
if self.outpost: if self.outpost:
OutpostState.for_channel(self.outpost, self.channel_name).delete() 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): def receive_json(self, content: Data):
msg = from_dict(WebsocketMessage, content) msg = from_dict(WebsocketMessage, content)

View File

@ -26,5 +26,5 @@ def invalidate_policy_cache(sender, instance, **_):
cache.delete_many(keys) cache.delete_many(keys)
LOGGER.debug("Invalidating policy cache", policy=instance, keys=total) LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
# Also delete user application cache # Also delete user application cache
keys = cache.keys(user_app_cache_key("*")) keys = cache.keys(user_app_cache_key("*")) or []
cache.delete_many(keys) cache.delete_many(keys)

View File

@ -74,7 +74,7 @@ class SAMLFlowFinalView(ChallengeStageView):
return super().get( return super().get(
self.request, self.request,
**{ **{
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-autosubmit", "component": "ak-stage-autosubmit",
"title": "Redirecting to %(app)s..." % {"app": application.name}, "title": "Redirecting to %(app)s..." % {"app": application.name},
"url": provider.acs_url, "url": provider.acs_url,

View File

@ -150,7 +150,6 @@ SWAGGER_SETTINGS = {
REST_FRAMEWORK = { REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination", "DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
"PAGE_SIZE": 100, "PAGE_SIZE": 100,
"DATETIME_FORMAT": "%s",
"DEFAULT_FILTER_BACKENDS": [ "DEFAULT_FILTER_BACKENDS": [
"rest_framework_guardian.filters.ObjectPermissionsFilter", "rest_framework_guardian.filters.ObjectPermissionsFilter",
"django_filters.rest_framework.DjangoFilterBackend", "django_filters.rest_framework.DjangoFilterBackend",

View File

@ -15,9 +15,11 @@ class OAuthSourceForm(forms.ModelForm):
self.fields["authentication_flow"].queryset = Flow.objects.filter( self.fields["authentication_flow"].queryset = Flow.objects.filter(
designation=FlowDesignation.AUTHENTICATION designation=FlowDesignation.AUTHENTICATION
) )
self.fields["authentication_flow"].required = True
self.fields["enrollment_flow"].queryset = Flow.objects.filter( self.fields["enrollment_flow"].queryset = Flow.objects.filter(
designation=FlowDesignation.ENROLLMENT designation=FlowDesignation.ENROLLMENT
) )
self.fields["enrollment_flow"].required = True
if hasattr(self.Meta, "overrides"): if hasattr(self.Meta, "overrides"):
for overide_field, overide_value in getattr(self.Meta, "overrides").items(): for overide_field, overide_value in getattr(self.Meta, "overrides").items():
self.fields[overide_field].initial = overide_value self.fields[overide_field].initial = overide_value

View File

@ -4,6 +4,7 @@ from typing import Any, Optional
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -151,6 +152,8 @@ class OAuthCallback(OAuthClientMixin, View):
PLAN_CONTEXT_REDIRECT: final_redirect, 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 # We run the Flow planner here so we can pass the Pending user in the context
planner = FlowPlanner(flow) planner = FlowPlanner(flow)
plan = planner.plan(self.request, kwargs) plan = planner.plan(self.request, kwargs)
@ -233,6 +236,9 @@ class OAuthCallback(OAuthClientMixin, View):
PLAN_CONTEXT_SOURCES_OAUTH_ACCESS: access, PLAN_CONTEXT_SOURCES_OAUTH_ACCESS: access,
} }
# We run the Flow planner here so we can pass the Pending user in the context # 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) planner = FlowPlanner(source.enrollment_flow)
plan = planner.plan(self.request, context) plan = planner.plan(self.request, context)
plan.append(in_memory_stage(PostUserEnrollmentStage)) plan.append(in_memory_stage(PostUserEnrollmentStage))

View File

@ -1,5 +1,8 @@
"""AuthenticatorStaticStage API Views""" """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.flows.api.stages import StageSerializer
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
@ -19,3 +22,39 @@ class AuthenticatorStaticStageViewSet(ModelViewSet):
queryset = AuthenticatorStaticStage.objects.all() queryset = AuthenticatorStaticStage.objects.all()
serializer_class = AuthenticatorStaticStageSerializer 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"]

View File

@ -31,7 +31,7 @@ class AuthenticatorStaticStageView(ChallengeStageView):
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS] tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
return AuthenticatorStaticChallenge( return AuthenticatorStaticChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-authenticator-static", "component": "ak-stage-authenticator-static",
"codes": [token.token for token in tokens], "codes": [token.token for token in tokens],
} }

View File

@ -1,5 +1,8 @@
"""AuthenticatorTOTPStage API Views""" """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.flows.api.stages import StageSerializer
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
@ -19,3 +22,41 @@ class AuthenticatorTOTPStageViewSet(ModelViewSet):
queryset = AuthenticatorTOTPStage.objects.all() queryset = AuthenticatorTOTPStage.objects.all()
serializer_class = AuthenticatorTOTPStageSerializer 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"]

View File

@ -51,7 +51,7 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE] device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
return AuthenticatorTOTPChallenge( return AuthenticatorTOTPChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-authenticator-totp", "component": "ak-stage-authenticator-totp",
"config_url": device.config_url, "config_url": device.config_url,
} }

View File

@ -145,7 +145,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
challenges = self.request.session["device_challenges"] challenges = self.request.session["device_challenges"]
return AuthenticatorChallenge( return AuthenticatorChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-authenticator-validate", "component": "ak-stage-authenticator-validate",
"device_challenges": challenges, "device_challenges": challenges,
} }

View File

@ -1,8 +1,13 @@
"""AuthenticateWebAuthnStage API Views""" """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.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): class AuthenticateWebAuthnStageSerializer(StageSerializer):
@ -19,3 +24,41 @@ class AuthenticateWebAuthnStageViewSet(ModelViewSet):
queryset = AuthenticateWebAuthnStage.objects.all() queryset = AuthenticateWebAuthnStage.objects.all()
serializer_class = AuthenticateWebAuthnStageSerializer 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"]

View File

@ -122,7 +122,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
return AuthenticatorWebAuthnChallenge( return AuthenticatorWebAuthnChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-authenticator-webauthn", "component": "ak-stage-authenticator-webauthn",
"registration": make_credential_options.registration_dict, "registration": make_credential_options.registration_dict,
} }

View File

@ -63,7 +63,7 @@ class CaptchaStageView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:
return CaptchaChallenge( return CaptchaChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-captcha", "component": "ak-stage-captcha",
"site_key": self.executor.current_stage.public_key, "site_key": self.executor.current_stage.public_key,
} }

View File

@ -38,7 +38,7 @@ class ConsentStageView(ChallengeStageView):
def get_challenge(self) -> Challenge: def get_challenge(self) -> Challenge:
challenge = ConsentChallenge( challenge = ConsentChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-consent", "component": "ak-stage-consent",
} }
) )

View File

@ -24,7 +24,7 @@ class DummyStageView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge: def get_challenge(self, *args, **kwargs) -> Challenge:
return DummyChallenge( return DummyChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "", "component": "",
"title": self.executor.current_stage.name, "title": self.executor.current_stage.name,
} }

View File

@ -94,7 +94,7 @@ class EmailStageView(ChallengeStageView):
def get_challenge(self) -> Challenge: def get_challenge(self) -> Challenge:
challenge = EmailChallenge( challenge = EmailChallenge(
data={"type": ChallengeTypes.native, "component": "ak-stage-email"} data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
) )
return challenge return challenge

View File

@ -78,7 +78,7 @@ class IdentificationStageView(ChallengeStageView):
current_stage: IdentificationStage = self.executor.current_stage current_stage: IdentificationStage = self.executor.current_stage
challenge = IdentificationChallenge( challenge = IdentificationChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-identification", "component": "ak-stage-identification",
"primary_action": _("Log in"), "primary_action": _("Log in"),
"input_type": "text", "input_type": "text",

View File

@ -78,7 +78,7 @@ class PasswordStageView(ChallengeStageView):
def get_challenge(self) -> Challenge: def get_challenge(self) -> Challenge:
challenge = PasswordChallenge( challenge = PasswordChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-password", "component": "ak-stage-password",
} }
) )

View File

@ -164,7 +164,7 @@ class PromptStageView(ChallengeStageView):
fields = list(self.executor.current_stage.fields.all().order_by("order")) fields = list(self.executor.current_stage.fields.all().order_by("order"))
challenge = PromptChallenge( challenge = PromptChallenge(
data={ data={
"type": ChallengeTypes.native, "type": ChallengeTypes.native.value,
"component": "ak-stage-prompt", "component": "ak-stage-prompt",
"fields": [PromptSerializer(field).data for field in fields], "fields": [PromptSerializer(field).data for field in fields],
}, },

View File

@ -279,6 +279,7 @@ stages:
displayName: Build static files for e2e displayName: Build static files for e2e
inputs: inputs:
script: | 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 cd web
npm i npm i
npm run build npm run build

View File

@ -19,7 +19,7 @@ services:
networks: networks:
- internal - internal
server: server:
image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.3} image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.3}
command: server command: server
environment: environment:
AUTHENTIK_REDIS__HOST: redis AUTHENTIK_REDIS__HOST: redis
@ -27,9 +27,11 @@ services:
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
# AUTHENTIK_ERROR_REPORTING__ENABLED: true
volumes: volumes:
- ./media:/media - ./media:/media
- ./custom-templates:/templates - ./custom-templates:/templates
- geoip:/geoip
ports: ports:
- 8000 - 8000
networks: networks:
@ -45,7 +47,7 @@ services:
env_file: env_file:
- .env - .env
worker: worker:
image: beryju/authentik:${AUTHENTIK_TAG:-2021.3.3} image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.3.3}
command: worker command: worker
networks: networks:
- internal - internal
@ -55,14 +57,16 @@ services:
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
# AUTHENTIK_ERROR_REPORTING__ENABLED: true
volumes: volumes:
- ./backups:/backups - ./backups:/backups
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./custom-templates:/templates - ./custom-templates:/templates
- geoip:/geoip
env_file: env_file:
- .env - .env
static: static:
image: beryju/authentik-static:${AUTHENTIK_TAG:-2021.3.3} image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.3.3}
networks: networks:
- internal - internal
labels: labels:
@ -91,10 +95,21 @@ services:
- "127.0.0.1:8080:8080" - "127.0.0.1:8080:8080"
networks: networks:
- internal - internal
geoipupdate:
image: "maxmindinc/geoipupdate:latest"
volumes:
- "geoip:/usr/share/GeoIP"
environment:
GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
GEOIPUPDATE_FREQUENCY: "8"
env_file:
- .env
volumes: volumes:
database: database:
driver: local driver: local
geoip:
driver: local
networks: networks:
internal: {} internal: {}

View File

@ -4,7 +4,7 @@
|-----------------------------------|-------------------------|-------------| |-----------------------------------|-------------------------|-------------|
| image.name | beryju/authentik | Image used to run the authentik server and worker | | image.name | beryju/authentik | Image used to run the authentik server and worker |
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) | | image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
| image.tag | 2021.3.3 | Image tag | | image.tag | 2021.3.3 | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments | | image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment | | serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment | | workerReplicas | 1 | Replicas for the Worker deployment |
@ -22,6 +22,10 @@
| config.email.use_ssl | false | Enable SSL | | config.email.use_ssl | false | Enable SSL |
| config.email.timeout | 10 | SMTP Timeout | | config.email.timeout | 10 | SMTP Timeout |
| config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain | | config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain |
| 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.accessKey | | Optionally enable S3 Backup, Access Key |
| backup.secretKey | | Optionally enable S3 Backup, Secret Key | | backup.secretKey | | Optionally enable S3 Backup, Secret Key |
| backup.bucket | | Optionally enable S3 Backup, Bucket | | backup.bucket | | Optionally enable S3 Backup, Bucket |

View 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 }}

View 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 }}

View 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 }}

View 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 }}

View 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 }}

View File

@ -88,9 +88,17 @@ spec:
secretKeyRef: secretKeyRef:
name: "{{ .Release.Name }}-postgresql" name: "{{ .Release.Name }}-postgresql"
key: "postgresql-password" key: "postgresql-password"
{{ if .Values.geoip.enabled -}}
- name: AUTHENTIK_AUTHENTIK__GEOIP
value: /geoip/GeoLite2-City.mmdb
{{- end }}
volumeMounts: volumeMounts:
- name: authentik-uploads - name: authentik-uploads
mountPath: /media mountPath: /media
{{ if .Values.geoip.enabled -}}
- name: geoip
mountPath: /geoip
{{- end }}
ports: ports:
- name: http - name: http
containerPort: 8000 containerPort: 8000
@ -116,3 +124,8 @@ spec:
- name: authentik-uploads - name: authentik-uploads
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ include "authentik.fullname" . }}-uploads claimName: {{ include "authentik.fullname" . }}-uploads
{{ if .Values.geoip.enabled -}}
- name: geoip
persistentVolumeClaim:
claimName: {{ include "authentik.fullname" . }}-geoip
{{- end }}

View 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 }}

View File

@ -68,6 +68,15 @@ spec:
secretKeyRef: secretKeyRef:
name: "{{ .Release.Name }}-postgresql" name: "{{ .Release.Name }}-postgresql"
key: "postgresql-password" 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: resources:
requests: requests:
cpu: 150m cpu: 150m
@ -75,3 +84,9 @@ spec:
limits: limits:
cpu: 300m cpu: 300m
memory: 600M memory: 600M
{{ if .Values.geoip.enabled -}}
volumes:
- name: geoip
persistentVolumeClaim:
claimName: {{ include "authentik.fullname" . }}-geoip
{{- end -}}

View File

@ -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

View File

@ -14,6 +14,9 @@ workerReplicas: 1
# Enable the Kubernetes integration which lets authentik deploy outposts into kubernetes # Enable the Kubernetes integration which lets authentik deploy outposts into kubernetes
kubernetesIntegration: true kubernetesIntegration: true
monitoring:
enabled: true
config: config:
# Optionally specify fixed secret_key, otherwise generated automatically # Optionally specify fixed secret_key, otherwise generated automatically
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o # 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 # Email address authentik will send from, should have a correct @domain
from: authentik@localhost from: authentik@localhost
# Enable MaxMind GeoIP
geoip:
enabled: false
accountId: ""
licenseKey: ""
image: maxmindinc/geoipupdate:latest
# Enable Database Backups to S3 # Enable Database Backups to S3
# backup: # backup:
# accessKey: access-key # accessKey: access-key

View File

@ -33,7 +33,7 @@ stages:
- task: PublishPipelineArtifact@1 - task: PublishPipelineArtifact@1
inputs: inputs:
targetPath: 'outpost/pkg/' targetPath: 'outpost/pkg/'
artifact: 'swagger_client' artifact: 'go_swagger_client'
publishLocation: 'pipeline' publishLocation: 'pipeline'
- stage: lint - stage: lint
jobs: jobs:
@ -51,7 +51,7 @@ stages:
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: 'swagger_client' artifactName: 'go_swagger_client'
path: "outpost/pkg/" path: "outpost/pkg/"
- task: CmdLine@2 - task: CmdLine@2
inputs: inputs:
@ -70,7 +70,7 @@ stages:
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: 'swagger_client' artifactName: 'go_swagger_client'
path: "outpost/pkg/" path: "outpost/pkg/"
- task: Go@0 - task: Go@0
inputs: inputs:
@ -89,7 +89,7 @@ stages:
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: 'swagger_client' artifactName: 'go_swagger_client'
path: "outpost/pkg/" path: "outpost/pkg/"
- task: Bash@3 - task: Bash@3
inputs: inputs:

View File

@ -24,7 +24,7 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
github.com/recws-org/recws v1.2.1 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/afero v1.5.1 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect

View File

@ -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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= 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.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 h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=

View File

@ -1,4 +1,4 @@
FROM golang:1.16.0 AS builder FROM golang:1.16.2 AS builder
WORKDIR /work WORKDIR /work

File diff suppressed because it is too large Load Diff

View 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",
)

View File

@ -3,11 +3,13 @@ from shutil import rmtree
from tempfile import mkdtemp from tempfile import mkdtemp
from time import sleep from time import sleep
import yaml
from django.test import TestCase from django.test import TestCase
from docker import DockerClient, from_env from docker import DockerClient, from_env
from docker.models.containers import Container from docker.models.containers import Container
from docker.types.healthcheck import Healthcheck from docker.types.healthcheck import Healthcheck
from authentik import __version__
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.outposts.apps import AuthentikOutpostConfig from authentik.outposts.apps import AuthentikOutpostConfig
@ -93,3 +95,14 @@ class OutpostDockerTests(TestCase):
controller = DockerController(self.outpost, self.service_connection) controller = DockerController(self.outpost, self.service_connection)
controller.up() controller.up()
controller.down() 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__}",
)

View 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__}",
)

View File

@ -9,7 +9,7 @@ from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesCont
from authentik.providers.proxy.models import ProxyProvider from authentik.providers.proxy.models import ProxyProvider
class TestControllers(TestCase): class TestProxyKubernetes(TestCase):
"""Test Controllers""" """Test Controllers"""
def setUp(self): def setUp(self):

View File

@ -4,3 +4,8 @@ node_modules
dist dist
# don't lint nyc coverage output # don't lint nyc coverage output
coverage coverage
# don't lint generated code
src/api/apis
src/api/models
src/api/index.ts
src/api/runtime.ts

View File

@ -10,6 +10,25 @@ variables:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }} branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
stages: stages:
- stage: generate
jobs:
- job: swagger_generate
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: CmdLine@2
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
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'web/src/api/'
artifact: 'ts_swagger_client'
publishLocation: 'pipeline'
- stage: lint - stage: lint
jobs: jobs:
- job: eslint - job: eslint
@ -20,6 +39,11 @@ stages:
inputs: inputs:
versionSpec: '12.x' versionSpec: '12.x'
displayName: 'Install Node.js' displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1 - task: Npm@1
inputs: inputs:
command: 'install' command: 'install'
@ -37,6 +61,11 @@ stages:
inputs: inputs:
versionSpec: '12.x' versionSpec: '12.x'
displayName: 'Install Node.js' displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1 - task: Npm@1
inputs: inputs:
command: 'install' command: 'install'
@ -56,6 +85,11 @@ stages:
inputs: inputs:
versionSpec: '12.x' versionSpec: '12.x'
displayName: 'Install Node.js' displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1 - task: Npm@1
inputs: inputs:
command: 'install' command: 'install'
@ -71,16 +105,21 @@ stages:
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
- task: Bash@3 - task: DownloadPipelineArtifact@2
inputs: inputs:
targetType: 'inline' buildType: 'current'
script: | artifactName: 'ts_swagger_client'
python ./scripts/az_do_set_branch.py path: "web/src/api/"
- task: Docker@2 - task: Bash@3
inputs: inputs:
containerRegistry: 'beryjuorg-harbor' targetType: 'inline'
repository: 'authentik/static' script: |
command: 'buildAndPush' python ./scripts/az_do_set_branch.py
Dockerfile: 'web/Dockerfile' - task: Docker@2
tags: "gh-$(branchName)" inputs:
buildContext: 'web/' containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/static'
command: 'buildAndPush'
Dockerfile: 'web/Dockerfile'
tags: "gh-$(branchName)"
buildContext: 'web/'

318
web/package-lock.json generated
View File

@ -103,9 +103,9 @@
} }
}, },
"@patternfly/patternfly": { "@patternfly/patternfly": {
"version": "4.87.3", "version": "4.90.5",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.87.3.tgz", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz",
"integrity": "sha512-hDNMPa7B1zKD8LWFZO4SS5hC/N+yvuci2sAn8HJd+EIbAvbMAUkRsyZ0/XO3BG3RVtpSlgq7q8x1pAHC/FTFuA==" "integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw=="
}, },
"@rollup/plugin-typescript": { "@rollup/plugin-typescript": {
"version": "8.2.0", "version": "8.2.0",
@ -143,27 +143,27 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.2.tgz",
"integrity": "sha512-OAikFZ9EimD3noxMp8tA6Cf6qJcQ2U8k5QSgTPwdx+09nZOGJzbRFteK7WWmrS93ZJdzN61lpSQbg5v+bmmfbQ==", "integrity": "sha512-K5UGyEePtVPZIFMoiRafhd4Ov0M1kdozVsVKIPZrOpJyjQdPNX+fYDNL/h0nVmgOlE2S/uu4fl4mEfe/6aLShw==",
"requires": { "requires": {
"@sentry/core": "6.2.1", "@sentry/core": "6.2.2",
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"@sentry/utils": "6.2.1", "@sentry/utils": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
"@sentry/types": { "@sentry/types": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA==" "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==", "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": { "requires": {
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
@ -175,48 +175,48 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.2.tgz",
"integrity": "sha512-jPqQEtafxxDtLONhCbTHh/Uq8mZRhsfbwJTSVYfPVEe/ELfFZLQK7tP6rOh7zEWKbTkE0mE6XcaoH3ZRAhgrqg==", "integrity": "sha512-qqWbvvXtymfXh7N5eEvk97MCnMURuyFIgqWdVD4MQM6yIfDCy36CyGfuQ3ViHTLZGdIfEOhLL9/f4kzf1RzqBA==",
"requires": { "requires": {
"@sentry/hub": "6.2.1", "@sentry/hub": "6.2.2",
"@sentry/minimal": "6.2.1", "@sentry/minimal": "6.2.2",
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"@sentry/utils": "6.2.1", "@sentry/utils": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
"@sentry/hub": { "@sentry/hub": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
"integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==", "integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
"requires": { "requires": {
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"@sentry/utils": "6.2.1", "@sentry/utils": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
"integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==", "integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
"requires": { "requires": {
"@sentry/hub": "6.2.1", "@sentry/hub": "6.2.2",
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA==" "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==", "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": { "requires": {
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
@ -228,12 +228,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
"integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==", "integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
"requires": { "requires": {
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"@sentry/utils": "6.2.1", "@sentry/utils": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -245,12 +245,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
"integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==", "integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
"requires": { "requires": {
"@sentry/hub": "6.2.1", "@sentry/hub": "6.2.2",
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -262,14 +262,14 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.2.tgz",
"integrity": "sha512-bvStY1SnL08wkSeVK3j9K5rivQQJdKFCPR2VYRFOCaUoleZ6ChPUnBvxQ/E2LXc0hk/y/wo1q4r5B0dfCCY+bQ==", "integrity": "sha512-mAkPoqtofNfka/u9rOVVDQPaEoTmr0AQh654g9ZqsaqsOJLKjB4FDLVNubWs90fjeKqHiYkI3ZHPak2TzHBPkw==",
"requires": { "requires": {
"@sentry/hub": "6.2.1", "@sentry/hub": "6.2.2",
"@sentry/minimal": "6.2.1", "@sentry/minimal": "6.2.2",
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"@sentry/utils": "6.2.1", "@sentry/utils": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -281,16 +281,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA==" "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.2.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==", "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": { "requires": {
"@sentry/types": "6.2.1", "@sentry/types": "6.2.2",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -310,12 +310,13 @@
} }
}, },
"@types/clean-css": { "@types/clean-css": {
"version": "4.2.2", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.3.tgz",
"integrity": "sha512-xiTJn3bmDh1lA8c6iVJs4ZhHw+pcmxXlJQXOB6G1oULaak8rmarIeFKI4aTJ7849dEhaO612wgIualZfbxTJwA==", "integrity": "sha512-ET0ldU/vpXecy5vO8JRIhtJWSrk1vzXdJcp3Bjf8bARZynl6vfkhEKY/A7njfNIRlmyTGuVFuqnD6I3tOGdXpQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*",
"source-map": "^0.6.0"
} }
}, },
"@types/codemirror": { "@types/codemirror": {
@ -404,22 +405,22 @@
} }
}, },
"@types/uglify-js": { "@types/uglify-js": {
"version": "3.11.0", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.0.tgz", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz",
"integrity": "sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q==", "integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"source-map": "^0.6.1" "source-map": "^0.6.1"
} }
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.17.0.tgz",
"integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==", "integrity": "sha512-/fKFDcoHg8oNan39IKFOb5WmV7oWhQe1K6CDaAVfJaNWEhmfqlA24g+u1lqU5bMH7zuNasfMId4LaYWC5ijRLw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.16.1", "@typescript-eslint/experimental-utils": "4.17.0",
"@typescript-eslint/scope-manager": "4.16.1", "@typescript-eslint/scope-manager": "4.17.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",
@ -429,112 +430,55 @@
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.17.0.tgz",
"integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==", "integrity": "sha512-ZR2NIUbnIBj+LGqCFGQ9yk2EBQrpVVFOh9/Kd0Lm6gLpSAcCuLLe5lUCibKGCqyH9HPwYC0GIJce2O1i8VYmWA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.16.1", "@typescript-eslint/scope-manager": "4.17.0",
"@typescript-eslint/types": "4.16.1", "@typescript-eslint/types": "4.17.0",
"@typescript-eslint/typescript-estree": "4.16.1", "@typescript-eslint/typescript-estree": "4.17.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.0.0"
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.17.0.tgz",
"integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==", "integrity": "sha512-KYdksiZQ0N1t+6qpnl6JeK9ycCFprS9xBAiIrw4gSphqONt8wydBw4BXJi3C11ywZmyHulvMaLjWsxDjUSDwAw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.16.1", "@typescript-eslint/scope-manager": "4.17.0",
"@typescript-eslint/types": "4.16.1", "@typescript-eslint/types": "4.17.0",
"@typescript-eslint/typescript-estree": "4.16.1", "@typescript-eslint/typescript-estree": "4.17.0",
"debug": "^4.1.1" "debug": "^4.1.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz",
"integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1"
}
},
"@typescript-eslint/types": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz",
"integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz",
"integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz",
"integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"eslint-visitor-keys": "^2.0.0"
}
},
"globby": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz",
"integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
}
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.17.0.tgz",
"integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==", "integrity": "sha512-OJ+CeTliuW+UZ9qgULrnGpPQ1bhrZNFpfT/Bc0pzNeyZwMik7/ykJ0JHnQ7krHanFN9wcnPK89pwn84cRUmYjw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.16.1", "@typescript-eslint/types": "4.17.0",
"@typescript-eslint/visitor-keys": "4.16.1" "@typescript-eslint/visitor-keys": "4.17.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.17.0.tgz",
"integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", "integrity": "sha512-RN5z8qYpJ+kXwnLlyzZkiJwfW2AY458Bf8WqllkondQIcN2ZxQowAToGSd9BlAUZDB5Ea8I6mqL2quGYCLT+2g==",
"dev": true "dev": true
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.17.0.tgz",
"integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==", "integrity": "sha512-lRhSFIZKUEPPWpWfwuZBH9trYIEJSI0vYsrxbvVvNyIUDoKWaklOAelsSkeh3E2VBSZiNe9BZ4E5tYBZbUczVQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.16.1", "@typescript-eslint/types": "4.17.0",
"@typescript-eslint/visitor-keys": "4.16.1", "@typescript-eslint/visitor-keys": "4.17.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"globby": "^11.0.1", "globby": "^11.0.1",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -559,12 +503,12 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.16.1", "version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.17.0.tgz",
"integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", "integrity": "sha512-WfuMN8mm5SSqXuAr9NM+fItJ0SVVphobWYkWOwQ1odsfC014Vdxk/92t4JwS1Q6fCA/ABfCKpa3AVtpUKTNKGQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.16.1", "@typescript-eslint/types": "4.17.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
@ -2280,16 +2224,16 @@
} }
}, },
"minify-html-literals": { "minify-html-literals": {
"version": "1.3.2", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.2.tgz", "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz",
"integrity": "sha512-DBdi0md84vjvwmLoo9xleFV5FkhzOwfKBqcmoVFL54c9CFlSBtG9KTKEQqiwscB+acewculqys1cDnwyrYlNtg==", "integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/html-minifier": "^3.5.3", "@types/html-minifier": "^3.5.3",
"clean-css": "^4.2.1", "clean-css": "^4.2.1",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"magic-string": "^0.25.0", "magic-string": "^0.25.0",
"parse-literals": "^1.2.0" "parse-literals": "^1.2.1"
} }
}, },
"minimatch": { "minimatch": {
@ -2489,20 +2433,12 @@
} }
}, },
"parse-literals": { "parse-literals": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz",
"integrity": "sha512-gh4zPwvFSXx9ginX8lu9MP3OPHN3VV12PXI8IXD6oMCklFqM82pfbU9e/PKf9r7oLpbqlDSDyHYSVlxxuq3Iew==", "integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"typescript": "^2.9.2 || ^3.0.0" "typescript": "^2.9.2 || ^3.0.0 || ^4.0.0"
},
"dependencies": {
"typescript": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
}
} }
}, },
"parse5": { "parse5": {
@ -2717,9 +2653,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.40.0", "version": "2.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.40.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.41.1.tgz",
"integrity": "sha512-WiOGAPbXoHu+TOz6hyYUxIksOwsY/21TRWoO593jgYt8mvYafYqQl+axaA8y1z2HFazNUUrsMSjahV2A6/2R9A==", "integrity": "sha512-nepLFAW5W71/MWpS2Yr7r31eS7HRfYg2RXnxb6ehqN9zY42yACxKtEfb4xq8SmNfUohAzGMcyl6jkwdLOAiUbg==",
"requires": { "requires": {
"fsevents": "~2.3.1" "fsevents": "~2.3.1"
} }
@ -2801,12 +2737,12 @@
} }
}, },
"rollup-plugin-minify-html-literals": { "rollup-plugin-minify-html-literals": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.5.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.6.tgz",
"integrity": "sha512-x4FzCnbBpYdme7MQDS3+18CvYLqakAtM/JmA3hqXplwzMeZWW3l14KU7H33RhJlHH8Klgv49hGtBRLWLfjCudw==", "integrity": "sha512-JRq2fjlCTiw0zu+1Sy3ClHGCxA79dWGr4HLHWSQgd060StVW9fBVksuj8Xw/suPkNSGClJf/4xNQ1MF6JeXPaw==",
"dev": true, "dev": true,
"requires": { "requires": {
"minify-html-literals": "^1.3.2", "minify-html-literals": "^1.3.5",
"rollup-pluginutils": "^2.8.2" "rollup-pluginutils": "^2.8.2"
} }
}, },
@ -3383,15 +3319,15 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "4.2.2", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "3.11.0", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.0.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.0.tgz",
"integrity": "sha512-e1KQFRCpOxnrJsJVqDUCjURq+wXvIn7cK2sRAx9XL3HYLL9aezOP4Pb1+Y3/o693EPk111Yj2Q+IUXxcpHlygQ==", "integrity": "sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==",
"dev": true "dev": true
}, },
"union-value": { "union-value": {

View File

@ -11,9 +11,9 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2", "@fortawesome/fontawesome-free": "^5.15.2",
"@patternfly/patternfly": "^4.87.3", "@patternfly/patternfly": "^4.90.5",
"@sentry/browser": "^6.2.1", "@sentry/browser": "^6.2.2",
"@sentry/tracing": "^6.2.1", "@sentry/tracing": "^6.2.2",
"@types/chart.js": "^2.9.31", "@types/chart.js": "^2.9.31",
"@types/codemirror": "0.0.108", "@types/codemirror": "0.0.108",
"@types/grecaptcha": "^3.0.1", "@types/grecaptcha": "^3.0.1",
@ -24,7 +24,7 @@
"flowchart.js": "^1.15.0", "flowchart.js": "^1.15.0",
"lit-element": "^2.4.0", "lit-element": "^2.4.0",
"lit-html": "^1.3.0", "lit-html": "^1.3.0",
"rollup": "^2.40.0", "rollup": "^2.41.1",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
"rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-external-globals": "^0.6.1",
@ -33,17 +33,17 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^8.2.0", "@rollup/plugin-typescript": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^4.16.1", "@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.16.1", "@typescript-eslint/parser": "^4.17.0",
"eslint": "^7.21.0", "eslint": "^7.21.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-lit": "^1.3.0", "eslint-plugin-lit": "^1.3.0",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-minify-html-literals": "^1.2.5", "rollup-plugin-minify-html-literals": "^1.2.6",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"typescript": "^4.2.2" "typescript": "^4.2.3"
} }
} }

4
web/src/api/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
apis/**
models/**
index.ts
runtime.ts

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,167 @@
apis/AdminApi.ts
apis/CoreApi.ts
apis/CryptoApi.ts
apis/EventsApi.ts
apis/FlowsApi.ts
apis/OutpostsApi.ts
apis/PoliciesApi.ts
apis/PropertymappingsApi.ts
apis/ProvidersApi.ts
apis/RootApi.ts
apis/SourcesApi.ts
apis/StagesApi.ts
apis/index.ts
index.ts
models/Application.ts
models/AuthenticateWebAuthnStage.ts
models/AuthenticatorStaticStage.ts
models/AuthenticatorTOTPStage.ts
models/AuthenticatorValidateStage.ts
models/Cache.ts
models/CaptchaStage.ts
models/CertificateData.ts
models/CertificateKeyPair.ts
models/Challenge.ts
models/Config.ts
models/ConsentStage.ts
models/Coordinate.ts
models/DenyStage.ts
models/DockerServiceConnection.ts
models/DummyPolicy.ts
models/DummyStage.ts
models/EmailStage.ts
models/ErrorDetail.ts
models/Event.ts
models/EventMatcherPolicy.ts
models/EventTopPerUser.ts
models/ExpressionPolicy.ts
models/Flow.ts
models/FlowDiagram.ts
models/FlowStageBinding.ts
models/Group.ts
models/GroupMembershipPolicy.ts
models/HaveIBeenPwendPolicy.ts
models/IPReputation.ts
models/IdentificationStage.ts
models/InlineResponse200.ts
models/InlineResponse2001.ts
models/InlineResponse20010.ts
models/InlineResponse20011.ts
models/InlineResponse20012.ts
models/InlineResponse20013.ts
models/InlineResponse20014.ts
models/InlineResponse20015.ts
models/InlineResponse20016.ts
models/InlineResponse20017.ts
models/InlineResponse20018.ts
models/InlineResponse20019.ts
models/InlineResponse2002.ts
models/InlineResponse20020.ts
models/InlineResponse20021.ts
models/InlineResponse20022.ts
models/InlineResponse20023.ts
models/InlineResponse20024.ts
models/InlineResponse20025.ts
models/InlineResponse20026.ts
models/InlineResponse20027.ts
models/InlineResponse20028.ts
models/InlineResponse20029.ts
models/InlineResponse2003.ts
models/InlineResponse20030.ts
models/InlineResponse20031.ts
models/InlineResponse20032.ts
models/InlineResponse20033.ts
models/InlineResponse20034.ts
models/InlineResponse20035.ts
models/InlineResponse20036.ts
models/InlineResponse20037.ts
models/InlineResponse20038.ts
models/InlineResponse20039.ts
models/InlineResponse2004.ts
models/InlineResponse20040.ts
models/InlineResponse20041.ts
models/InlineResponse20042.ts
models/InlineResponse20043.ts
models/InlineResponse20044.ts
models/InlineResponse20045.ts
models/InlineResponse20046.ts
models/InlineResponse20047.ts
models/InlineResponse20048.ts
models/InlineResponse20049.ts
models/InlineResponse2005.ts
models/InlineResponse20050.ts
models/InlineResponse20051.ts
models/InlineResponse20052.ts
models/InlineResponse20053.ts
models/InlineResponse20054.ts
models/InlineResponse20055.ts
models/InlineResponse20056.ts
models/InlineResponse20057.ts
models/InlineResponse20058.ts
models/InlineResponse20059.ts
models/InlineResponse2006.ts
models/InlineResponse20060.ts
models/InlineResponse2007.ts
models/InlineResponse2008.ts
models/InlineResponse2009.ts
models/InlineResponse200Pagination.ts
models/Invitation.ts
models/InvitationStage.ts
models/KubernetesServiceConnection.ts
models/LDAPPropertyMapping.ts
models/LDAPSource.ts
models/LDAPSourceSyncStatus.ts
models/LoginMetrics.ts
models/Notification.ts
models/NotificationRule.ts
models/NotificationRuleGroup.ts
models/NotificationRuleGroupParent.ts
models/NotificationRuleTransports.ts
models/NotificationTransport.ts
models/NotificationTransportTest.ts
models/OAuth2Provider.ts
models/OAuth2ProviderSetupURLs.ts
models/OAuthSource.ts
models/OpenIDConnectConfiguration.ts
models/Outpost.ts
models/OutpostHealth.ts
models/PasswordExpiryPolicy.ts
models/PasswordPolicy.ts
models/PasswordStage.ts
models/Policy.ts
models/PolicyBinding.ts
models/PolicyBindingPolicy.ts
models/PolicyBindingUser.ts
models/PolicyBindingUserAkGroups.ts
models/PolicyBindingUserGroups.ts
models/PolicyBindingUserSources.ts
models/PolicyBindingUserUserPermissions.ts
models/Prompt.ts
models/PromptStage.ts
models/PropertyMapping.ts
models/Provider.ts
models/ProxyOutpostConfig.ts
models/ProxyProvider.ts
models/ReputationPolicy.ts
models/SAMLMetadata.ts
models/SAMLPropertyMapping.ts
models/SAMLProvider.ts
models/SAMLSource.ts
models/ScopeMapping.ts
models/ServiceConnection.ts
models/ServiceConnectionState.ts
models/Source.ts
models/Stage.ts
models/Task.ts
models/Token.ts
models/TokenView.ts
models/TypeCreate.ts
models/User.ts
models/UserDeleteStage.ts
models/UserLoginStage.ts
models/UserLogoutStage.ts
models/UserReputation.ts
models/UserWriteStage.ts
models/Version.ts
models/index.ts
runtime.ts

View File

@ -0,0 +1 @@
5.1.0-SNAPSHOT

View File

@ -1,32 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Provider } from "./Providers";
export class Application {
pk: string;
name: string;
slug: string;
provider?: Provider;
launch_url: string;
meta_launch_url: string;
meta_icon: string;
meta_description: string;
meta_publisher: string;
policies: string[];
constructor() {
throw Error();
}
static get(slug: string): Promise<Application> {
return DefaultClient.fetch<Application>(["core", "applications", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Application>> {
return DefaultClient.fetch<AKResponse<Application>>(["core", "applications"], filter);
}
static adminUrl(rest: string): string {
return `/administration/applications/${rest}`;
}
}

View File

@ -1,26 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
export class CertificateKeyPair {
pk: string;
name: string;
fingerprint: string;
cert_expiry: number;
cert_subject: string;
private_key_available: boolean;
constructor() {
throw Error();
}
static get(slug: string): Promise<CertificateKeyPair> {
return DefaultClient.fetch<CertificateKeyPair>(["crypto", "certificatekeypairs", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<CertificateKeyPair>> {
return DefaultClient.fetch<AKResponse<CertificateKeyPair>>(["crypto", "certificatekeypairs"], filter);
}
static adminUrl(rest: string): string {
return `/administration/crypto/certificates/${rest}`;
}
}

View File

@ -1,10 +1,3 @@
import { gettext } from "django";
import { showMessage } from "../elements/messages/MessageContainer";
import { getCookie } from "../utils";
import { NotFoundError, RequestError } from "./Error";
export const VERSION = "v2beta";
export interface QueryArguments { export interface QueryArguments {
page?: number; page?: number;
page_size?: number; page_size?: number;
@ -13,104 +6,27 @@ export interface QueryArguments {
export interface BaseInheritanceModel { export interface BaseInheritanceModel {
object_type: string; objectType: string;
verbose_name: string; verboseName: string;
verbose_name_plural: string; verboseNamePlural: string;
} }
export class Client { export interface AKPagination {
makeUrl(url: string[], query?: QueryArguments): string {
let builtUrl = `/api/${VERSION}/${url.join("/")}/`;
if (query) {
const queryString = Object.keys(query)
.filter((k) => query[k] !== null)
// we default to a string in query[k] as we've filtered out the null above
// this is just for type-hinting
.map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(query[k] || ""))
.join("&");
builtUrl += `?${queryString}`;
}
return builtUrl;
}
fetch<T>(url: string[], query?: QueryArguments): Promise<T> {
const finalUrl = this.makeUrl(url, query);
return fetch(finalUrl)
.then((r) => {
if (r.status > 300) {
switch (r.status) {
case 404:
throw new NotFoundError(`URL ${finalUrl} not found`);
default:
throw new RequestError(r.statusText);
}
}
return r;
})
.catch((e) => {
showMessage({
level_tag: "error",
message: gettext(`Unexpected error while fetching: ${e.toString()}`),
});
return e;
})
.then((r) => r.json())
.then((r) => <T>r);
}
private writeRequest<T>(url: string[], body: T, method: string, query?: QueryArguments): Promise<T> {
const finalUrl = this.makeUrl(url, query);
const csrftoken = getCookie("authentik_csrf");
const request = new Request(finalUrl, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"X-CSRFToken": csrftoken,
},
});
return fetch(request, {
method: method,
mode: "same-origin",
body: JSON.stringify(body),
})
.then((r) => {
if (r.status > 300) {
switch (r.status) {
case 404:
throw new NotFoundError(`URL ${finalUrl} not found`);
default:
throw new RequestError(r.statusText);
}
}
return r;
})
.then((r) => r.json())
.then((r) => <T>r);
}
update<T>(url: string[], body: T, query?: QueryArguments): Promise<T> {
return this.writeRequest(url, body, "PATCH", query);
}
}
export const DefaultClient = new Client();
export interface PBPagination {
next?: number; next?: number;
previous?: number; previous?: number;
count: number; count: number;
current: number; current: number;
total_pages: number; totalPages: number;
start_index: number; startIndex: number;
end_index: number; endIndex: number;
} }
export interface AKResponse<T> { export interface AKResponse<T> {
pagination: PBPagination; pagination: AKPagination;
results: Array<T>; results: Array<T>;
} }

View File

@ -1,42 +1,39 @@
import { DefaultClient } from "./Client";
import * as Sentry from "@sentry/browser"; import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing"; import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants"; import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors"; import { SentryIgnoredError } from "../common/errors";
import { Configuration } from "./runtime";
import { RootApi } from "./apis";
import { Config } from ".";
import { getCookie } from "../utils";
export class Config { export const DEFAULT_CONFIG = new Configuration({
branding_logo: string; basePath: "/api/v2beta",
branding_title: string; headers: {
"X-CSRFToken": getCookie("authentik_csrf"),
error_reporting_enabled: boolean;
error_reporting_environment: string;
error_reporting_send_pii: boolean;
constructor() {
throw Error();
} }
});
static get(): Promise<Config> { export function configureSentry(): Promise<Config> {
return DefaultClient.fetch<Config>(["root", "config"]).then((config) => { return new RootApi(DEFAULT_CONFIG).rootConfigList().then((config) => {
if (config.error_reporting_enabled) { if (config.errorReportingEnabled) {
Sentry.init({ Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8", dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`, release: `authentik@${VERSION}`,
integrations: [ integrations: [
new Integrations.BrowserTracing(), new Integrations.BrowserTracing(),
], ],
tracesSampleRate: 0.6, tracesSampleRate: 0.6,
environment: config.error_reporting_environment, environment: config.errorReportingEnvironment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) { beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {
if (hint.originalException instanceof SentryIgnoredError) { if (hint.originalException instanceof SentryIgnoredError) {
return null; return null;
} }
return event; return event;
}, },
}); });
console.debug("authentik/config: Sentry enabled."); console.debug("authentik/config: Sentry enabled.");
} }
return config; return config;
}); });
}
} }

View File

@ -1,30 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Event } from "./Events";
export class Notification {
pk: string;
severity: string;
body: string;
created: string;
event?: Event;
seen: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<Notification> {
return DefaultClient.fetch<Notification>(["events", "notifications", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Notification>> {
return DefaultClient.fetch<AKResponse<Notification>>(["events", "notifications"], filter);
}
static markSeen(pk: string): Promise<{seen: boolean}> {
return DefaultClient.update(["events", "notifications", pk], {
"seen": true
});
}
}

View File

@ -1,26 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Group } from "./Groups";
export class Rule {
pk: string;
name: string;
transports: string[];
severity: string;
group?: Group;
constructor() {
throw Error();
}
static get(pk: string): Promise<Rule> {
return DefaultClient.fetch<Rule>(["events", "rules", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Rule>> {
return DefaultClient.fetch<AKResponse<Rule>>(["events", "rules"], filter);
}
static adminUrl(rest: string): string {
return `/administration/events/rules/${rest}`;
}
}

View File

@ -1,25 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
export class Transport {
pk: string;
name: string;
mode: string;
mode_verbose: string;
webhook_url: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Transport> {
return DefaultClient.fetch<Transport>(["events", "transports", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Transport>> {
return DefaultClient.fetch<AKResponse<Transport>>(["events", "transports"], filter);
}
static adminUrl(rest: string): string {
return `/administration/events/transports/${rest}`;
}
}

View File

@ -1,4 +1,4 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client"; import { Event } from "./models";
export interface EventUser { export interface EventUser {
pk: number; pk: number;
@ -11,37 +11,7 @@ export interface EventContext {
[key: string]: EventContext | string | number | string[]; [key: string]: EventContext | string | number | string[];
} }
export class Event { export interface EventWithContext extends Event {
pk: string;
user: EventUser; user: EventUser;
action: string;
app: string;
context: EventContext; context: EventContext;
client_ip: string;
created: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Event> {
return DefaultClient.fetch<Event>(["events", "events", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Event>> {
return DefaultClient.fetch<AKResponse<Event>>(["events", "events"], filter);
}
// events/events/top_per_user/?filter_action=authorize_application
static topForUser(action: string): Promise<TopNEvent[]> {
return DefaultClient.fetch<TopNEvent[]>(["events", "events", "top_per_user"], {
"filter_action": action,
});
}
}
export interface TopNEvent {
application: { [key: string]: string};
counted_events: number;
unique_users: number;
} }

View File

@ -1,12 +1,4 @@
import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client"; import { ChallengeTypeEnum } from "./models";
import { TypeCreate } from "./Providers";
export enum ChallengeTypes {
native = "native",
response = "response",
shell = "shell",
redirect = "redirect",
}
export interface Error { export interface Error {
code: string; code: string;
@ -18,11 +10,12 @@ export interface ErrorDict {
} }
export interface Challenge { export interface Challenge {
type: ChallengeTypes; type: ChallengeTypeEnum;
component?: string; component?: string;
title?: string; title?: string;
response_errors?: ErrorDict; response_errors?: ErrorDict;
} }
export interface WithUserInfoChallenge extends Challenge { export interface WithUserInfoChallenge extends Challenge {
pending_user: string; pending_user: string;
pending_user_avatar: string; pending_user_avatar: string;
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
export interface ShellChallenge extends Challenge { export interface ShellChallenge extends Challenge {
body: string; body: string;
} }
export interface RedirectChallenge extends Challenge { export interface RedirectChallenge extends Challenge {
to: string; to: string;
} }
@ -44,104 +38,3 @@ export enum FlowDesignation {
Recovery = "recovery", Recovery = "recovery",
StageConfiguration = "stage_configuration", StageConfiguration = "stage_configuration",
} }
export class Flow {
pk: string;
policybindingmodel_ptr_id: string;
name: string;
slug: string;
title: string;
designation: FlowDesignation;
background: string;
stages: string[];
policies: string[];
cache_count: number;
constructor() {
throw Error();
}
static get(slug: string): Promise<Flow> {
return DefaultClient.fetch<Flow>(["flows", "instances", slug]);
}
static diagram(slug: string): Promise<{ diagram: string }> {
return DefaultClient.fetch<{ diagram: string }>(["flows", "instances", slug, "diagram"]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Flow>> {
return DefaultClient.fetch<AKResponse<Flow>>(["flows", "instances"], filter);
}
static cached(): Promise<number> {
return DefaultClient.fetch<{ count: number }>(["flows", "instances", "cached"]).then(r => {
return r.count;
});
}
static executor(slug: string): Promise<Challenge> {
return DefaultClient.fetch(["flows", "executor", slug]);
}
static adminUrl(rest: string): string {
return `/administration/flows/${rest}`;
}
}
export class Stage implements BaseInheritanceModel {
pk: string;
name: string;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
flow_set: Flow[];
constructor() {
throw Error();
}
static get(slug: string): Promise<Stage> {
return DefaultClient.fetch<Stage>(["stages", "all", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Stage>> {
return DefaultClient.fetch<AKResponse<Stage>>(["stages", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["stages", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/stages/${rest}`;
}
}
export class FlowStageBinding {
pk: string;
policybindingmodel_ptr_id: string;
target: string;
stage: string;
stage_obj: Stage;
evaluate_on_plan: boolean;
re_evaluate_policies: boolean;
order: number;
policies: string[];
constructor() {
throw Error();
}
static get(slug: string): Promise<FlowStageBinding> {
return DefaultClient.fetch<FlowStageBinding>(["flows", "bindings", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<FlowStageBinding>> {
return DefaultClient.fetch<AKResponse<FlowStageBinding>>(["flows", "bindings"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/bindings/${rest}`;
}
}

View File

@ -1,28 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
export class Group {
pk: string;
name: string;
is_superuser: boolean;
attributes: EventContext;
parent?: Group;
users: number[];
constructor() {
throw Error();
}
static get(pk: string): Promise<Group> {
return DefaultClient.fetch<Group>(["core", "groups", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
}
static adminUrl(rest: string): string {
return `/administration/groups/${rest}`;
}
}

View File

@ -1,27 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
import { User } from "./Users";
export class Invitation {
pk: string;
expires: number;
fixed_date: EventContext;
created_by: User;
constructor() {
throw Error();
}
static get(pk: string): Promise<Invitation> {
return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
}

View File

@ -1,79 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Provider, TypeCreate } from "./Providers";
export interface OutpostHealth {
last_seen: number;
version: string;
version_should: string;
version_outdated: boolean;
}
export class Outpost {
pk: string;
name: string;
providers: number[];
providers_obj: Provider[];
service_connection?: string;
_config: QueryArguments;
token_identifier: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<Outpost> {
return DefaultClient.fetch<Outpost>(["outposts", "outposts", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Outpost>> {
return DefaultClient.fetch<AKResponse<Outpost>>(["outposts", "outposts"], filter);
}
static health(pk: string): Promise<OutpostHealth[]> {
return DefaultClient.fetch<OutpostHealth[]>(["outposts", "outposts", pk, "health"]);
}
static adminUrl(rest: string): string {
return `/administration/outposts/${rest}`;
}
}
export interface OutpostServiceConnectionState {
version: string;
healthy: boolean;
}
export class OutpostServiceConnection {
pk: string;
name: string;
local: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<OutpostServiceConnection> {
return DefaultClient.fetch<OutpostServiceConnection>(["outposts", "service_connections", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<OutpostServiceConnection>> {
return DefaultClient.fetch<AKResponse<OutpostServiceConnection>>(["outposts", "service_connections", "all"], filter);
}
static state(pk: string): Promise<OutpostServiceConnectionState> {
return DefaultClient.fetch<OutpostServiceConnectionState>(["outposts", "service_connections", "all", pk, "state"]);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["outposts", "service_connections", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/outpost_service_connections/${rest}`;
}
}

View File

@ -1,38 +0,0 @@
import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Policy implements BaseInheritanceModel {
pk: string;
name: string;
execution_logging: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
bound_to: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Policy>> {
return DefaultClient.fetch<AKResponse<Policy>>(["policies", "all"], filter);
}
static cached(): Promise<number> {
return DefaultClient.fetch<{ count: number }>(["policies", "all", "cached"]).then(r => {
return r.count;
});
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["policies", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/policies/${rest}`;
}
}

View File

@ -1,31 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { Group } from "./Groups";
import { Policy } from "./Policies";
import { User } from "./Users";
export class PolicyBinding {
pk: string;
policy?: Policy;
group?: Group;
user?: User;
target: string;
enabled: boolean;
order: number;
timeout: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<PolicyBinding> {
return DefaultClient.fetch<PolicyBinding>(["policies", "bindings", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<PolicyBinding>> {
return DefaultClient.fetch<AKResponse<PolicyBinding>>(["policies", "bindings"], filter);
}
static adminUrl(rest: string): string {
return `/administration/policies/bindings/${rest}`;
}
}

View File

@ -1,30 +0,0 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Stage } from "./Flows";
export class Prompt {
pk: string;
field_key: string;
label: string;
type: string;
required: boolean;
placeholder: string;
order: number;
promptstage_set: Stage[];
constructor() {
throw Error();
}
static get(pk: string): Promise<Prompt> {
return DefaultClient.fetch<Prompt>(["stages", "prompt", "prompts", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Prompt>> {
return DefaultClient.fetch<AKResponse<Prompt>>(["stages", "prompt", "prompts"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages_prompts/${rest}`;
}
}

View File

@ -1,31 +0,0 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class PropertyMapping {
pk: string;
name: string;
expression: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(pk: string): Promise<PropertyMapping> {
return DefaultClient.fetch<PropertyMapping>(["propertymappings", "all", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<PropertyMapping>> {
return DefaultClient.fetch<AKResponse<PropertyMapping>>(["propertymappings", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["propertymappings", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/property-mappings/${rest}`;
}
}

View File

@ -1,40 +0,0 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
export interface TypeCreate {
name: string;
description: string;
link: string;
}
export class Provider implements BaseInheritanceModel {
pk: number;
name: string;
authorization_flow: string;
object_type: string;
assigned_application_slug?: string;
assigned_application_name?: string;
verbose_name: string;
verbose_name_plural: string;
constructor() {
throw Error();
}
static get(id: number): Promise<Provider> {
return DefaultClient.fetch<Provider>(["providers", "all", id.toString()]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Provider>> {
return DefaultClient.fetch<AKResponse<Provider>>(["providers", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["providers", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/providers/${rest}`;
}
}

View File

@ -1,34 +0,0 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Source implements BaseInheritanceModel {
pk: string;
name: string;
slug: string;
enabled: boolean;
authentication_flow: string;
enrollment_flow: string;
constructor() {
throw Error();
}
object_type: string;
verbose_name: string;
verbose_name_plural: string;
static get(slug: string): Promise<Source> {
return DefaultClient.fetch<Source>(["sources", "all", slug]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Source>> {
return DefaultClient.fetch<AKResponse<Source>>(["sources", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["sources", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/sources/${rest}`;
}
}

View File

@ -1,33 +0,0 @@
import { DefaultClient, QueryArguments } from "./Client";
export enum TaskStatus {
SUCCESSFUL = 1,
WARNING = 2,
ERROR = 4,
}
export class SystemTask {
task_name: string;
task_description: string;
task_finish_timestamp: number;
status: TaskStatus;
messages: string[];
constructor() {
throw Error();
}
static get(task_name: string): Promise<SystemTask> {
return DefaultClient.fetch<SystemTask>(["admin", "system_tasks", task_name]);
}
static list(filter?: QueryArguments): Promise<SystemTask[]> {
return DefaultClient.fetch<SystemTask[]>(["admin", "system_tasks"], filter);
}
static retry(task_name: string): string {
return DefaultClient.makeUrl(["admin", "system_tasks", task_name, "retry"]);
}
}

View File

@ -1,47 +0,0 @@
import { AKResponse, DefaultClient, QueryArguments } from "./Client";
import { User } from "./Users";
export enum TokenIntent {
INTENT_VERIFICATION = "verification",
INTENT_API = "api",
INTENT_RECOVERY = "recovery",
}
export class Token {
pk: string;
identifier: string;
intent: TokenIntent;
user: User;
description: string;
expires: number;
expiring: boolean;
constructor() {
throw Error();
}
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "tokens", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Token>> {
return DefaultClient.fetch<AKResponse<Token>>(["core", "tokens"], filter);
}
static adminUrl(rest: string): string {
return `/administration/tokens/${rest}`;
}
static userUrl(rest: string): string {
return `/-/user/tokens/${rest}`;
}
static getKey(identifier: string): Promise<string> {
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
(r) => r.key
);
}
}

View File

@ -1,45 +1,11 @@
import { DefaultClient, AKResponse, QueryArguments } from "./Client"; import { CoreApi } from "./apis";
import { DEFAULT_CONFIG } from "./Config";
import { User } from "./models";
let _globalMePromise: Promise<User>; let _globalMePromise: Promise<User>;
export function me(): Promise<User> {
export class User { if (!_globalMePromise) {
pk: number; _globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
username: string;
name: string;
is_superuser: boolean;
email: boolean;
avatar: string;
is_active: boolean;
last_login: number;
constructor() {
throw Error();
}
static get(pk: string): Promise<User> {
return DefaultClient.fetch<User>(["core", "users", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<User>> {
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], filter);
}
static adminUrl(rest: string): string {
return `/administration/users/${rest}`;
}
static me(): Promise<User> {
if (!_globalMePromise) {
_globalMePromise = DefaultClient.fetch<User>(["core", "users", "me"]);
}
return _globalMePromise;
}
static count(): Promise<number> {
return DefaultClient.fetch<AKResponse<User>>(["core", "users"], {
"page_size": 1
}).then(r => {
return r.pagination.count;
});
} }
return _globalMePromise;
} }

View File

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

97
web/src/api/legacy.ts Normal file
View File

@ -0,0 +1,97 @@
export class AdminURLManager {
static applications(rest: string): string {
return `/administration/applications/${rest}`;
}
static cryptoCertificates(rest: string): string {
return `/administration/crypto/certificates/${rest}`;
}
static policies(rest: string): string {
return `/administration/policies/${rest}`;
}
static policyBindings(rest: string): string {
return `/administration/policies/bindings/${rest}`;
}
static providers(rest: string): string {
return `/administration/providers/${rest}`;
}
static propertyMappings(rest: string): string {
return `/administration/property-mappings/${rest}`;
}
static outposts(rest: string): string {
return `/administration/outposts/${rest}`;
}
static outpostServiceConnections(rest: string): string {
return `/administration/outpost_service_connections/${rest}`;
}
static flows(rest: string): string {
return `/administration/flows/${rest}`;
}
static stages(rest: string): string {
return `/administration/stages/${rest}`;
}
static stagePrompts(rest: string): string {
return `/administration/stages_prompts/${rest}`;
}
static stageInvitations(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
static stageBindings(rest: string): string {
return `/administration/stages/bindings/${rest}`;
}
static sources(rest: string): string {
return `/administration/sources/${rest}`;
}
static tokens(rest: string): string {
return `/administration/tokens/${rest}`;
}
static eventRules(rest: string): string {
return `/administration/events/rules/${rest}`;
}
static eventTransports(rest: string): string {
return `/administration/events/transports/${rest}`;
}
static users(rest: string): string {
return `/administration/users/${rest}`;
}
static groups(rest: string): string {
return `/administration/groups/${rest}`;
}
}
export class UserURLManager {
static tokens(rest: string): string {
return `/-/user/tokens/${rest}`;
}
}
export class AppURLManager {
static sourceSAML(slug: string, rest: string): string {
return `/source/saml/${slug}/${rest}`;
}
static providerSAML(rest: string): string {
return `/application/saml/${rest}`;
}
}

View File

@ -1,43 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export interface OAuth2SetupURLs {
issuer?: string;
authorize: string;
token: string;
user_info: string;
provider_info?: string;
logout?: string;
}
export class OAuth2Provider extends Provider {
client_type: string
client_id: string;
client_secret: string;
token_validity: string;
include_claims_in_id_token: boolean;
jwt_alg: string;
rsa_key: string;
redirect_uris: string;
sub_mode: string;
issuer_mode: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<OAuth2Provider> {
return DefaultClient.fetch<OAuth2Provider>(["providers", "oauth2", id.toString()]);
}
static getLaunchURls(id: number): Promise<OAuth2SetupURLs> {
return DefaultClient.fetch(["providers", "oauth2", id.toString(), "setup_urls"]);
}
static appUrl(rest: string): string {
return `/application/oauth2/${rest}`;
}
}

View File

@ -1,30 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export class ProxyProvider extends Provider {
internal_host: string;
external_host: string;
internal_host_ssl_validation: boolean
certificate?: string;
skip_path_regex: string;
basic_auth_enabled: boolean;
basic_auth_password_attribute: string;
basic_auth_user_attribute: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<ProxyProvider> {
return DefaultClient.fetch<ProxyProvider>(["providers", "proxy", id.toString()]);
}
static getMetadata(id: number): Promise<{ metadata: string }> {
return DefaultClient.fetch(["providers", "proxy", id.toString(), "metadata"]);
}
static appUrl(rest: string): string {
return `/application/proxy/${rest}`;
}
}

View File

@ -1,33 +0,0 @@
import { DefaultClient } from "../Client";
import { Provider } from "../Providers";
export class SAMLProvider extends Provider {
acs_url: string;
audience: string;
issuer: string;
assertion_valid_not_before: string;
assertion_valid_not_on_or_after: string;
session_valid_not_on_or_after: string;
name_id_mapping?: string;
digest_algorithm: string;
signature_algorithm: string;
signing_kp?: string;
verification_kp?: string;
constructor() {
super();
throw Error();
}
static get(id: number): Promise<SAMLProvider> {
return DefaultClient.fetch<SAMLProvider>(["providers", "saml", id.toString()]);
}
static getMetadata(id: number): Promise<{ metadata: string }> {
return DefaultClient.fetch(["providers", "saml", id.toString(), "metadata"]);
}
static appUrl(rest: string): string {
return `/application/saml/${rest}`;
}
}

View File

@ -1,35 +0,0 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class LDAPSource extends Source {
server_uri: string;
bind_cn: string;
start_tls: boolean
base_dn: string;
additional_user_dn: string;
additional_group_dn: string;
user_object_filter: string;
group_object_filter: string;
group_membership_field: string;
object_uniqueness_field: string;
sync_users: boolean;
sync_users_password: boolean;
sync_groups: boolean;
sync_parent_group?: string;
property_mappings: string[];
property_mappings_group: string[];
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<LDAPSource> {
return DefaultClient.fetch<LDAPSource>(["sources", "ldap", slug]);
}
static syncStatus(slug: string): Promise<{ last_sync?: number }> {
return DefaultClient.fetch(["sources", "ldap", slug, "sync_status"]);
}
}

View File

@ -1,22 +0,0 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class OAuthSource extends Source {
provider_type: string;
request_token_url: string;
authorization_url: string;
access_token_url: string;
profile_url: string;
consumer_key: string;
callback_url: string;
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<OAuthSource> {
return DefaultClient.fetch<OAuthSource>(["sources", "oauth", slug]);
}
}

View File

@ -1,32 +0,0 @@
import { DefaultClient } from "../Client";
import { Source } from "../Sources";
export class SAMLSource extends Source {
issuer: string;
sso_url: string;
slo_url: string;
allow_idp_initiated: boolean;
name_id_policy: string;
binding_type: string
signing_kp?: string;
digest_algorithm: string;
signature_algorithm: string;
temporary_user_delete_after: string;
constructor() {
super();
throw Error();
}
static get(slug: string): Promise<SAMLSource> {
return DefaultClient.fetch<SAMLSource>(["sources", "saml", slug]);
}
static getMetadata(slug: string): Promise<{ metadata: string }> {
return DefaultClient.fetch(["sources", "saml", slug, "metadata"]);
}
static appUrl(slug: string, rest: string): string {
return `/source/saml/${slug}/${rest}`;
}
}

View File

@ -5,6 +5,12 @@ html {
--pf-c-nav__link--PaddingLeft: 0.5rem; --pf-c-nav__link--PaddingLeft: 0.5rem;
} }
html > input {
position: absolute;
top: -2000px;
left: -2000px;
}
.pf-c-page__header { .pf-c-page__header {
z-index: 0; z-index: 0;
} }
@ -83,7 +89,7 @@ select[multiple] {
/* ensure background on non-flow pages match */ /* ensure background on non-flow pages match */
.pf-c-background-image::before { .pf-c-background-image::before {
background-image: url("dist/assets/images/flow_background.jpg"); background-image: url("/static/dist/assets/images/flow_background.jpg");
background-position: center; background-position: center;
} }
@ -157,7 +163,7 @@ ak-message {
color: var(--ak-dark-foreground) !important; color: var(--ak-dark-foreground) !important;
} }
/* tabs, vertical */ /* tabs, vertical */
.pf-c-tabs__link { .pf-c-tabs.pf-m-vertical .pf-c-tabs__link {
background-color: var(--ak-dark-background-light); background-color: var(--ak-dark-background-light);
} }
/* table, on mobile */ /* table, on mobile */

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