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: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
steps:
- uses: actions/checkout@v1
- name: prepare ts api client
run: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View file

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

174
Pipfile.lock generated
View file

@ -18,45 +18,45 @@
"default": {
"aiohttp": {
"hashes": [
"sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0",
"sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6",
"sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf",
"sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9",
"sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e",
"sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0",
"sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329",
"sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2",
"sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40",
"sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a",
"sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4",
"sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de",
"sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9",
"sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9",
"sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb",
"sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076",
"sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de",
"sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907",
"sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d",
"sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536",
"sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d",
"sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54",
"sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc",
"sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212",
"sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9",
"sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d",
"sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b",
"sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7",
"sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81",
"sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c",
"sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895",
"sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297",
"sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb",
"sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe",
"sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242",
"sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0",
"sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2"
"sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe",
"sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe",
"sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5",
"sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8",
"sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd",
"sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb",
"sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c",
"sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87",
"sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0",
"sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290",
"sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5",
"sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287",
"sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde",
"sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf",
"sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8",
"sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16",
"sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf",
"sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809",
"sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213",
"sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f",
"sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013",
"sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b",
"sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9",
"sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5",
"sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb",
"sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df",
"sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4",
"sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439",
"sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f",
"sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22",
"sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f",
"sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5",
"sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970",
"sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009",
"sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc",
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
],
"version": "==3.7.4"
"version": "==3.7.4.post0"
},
"aioredis": {
"hashes": [
@ -116,18 +116,17 @@
},
"boto3": {
"hashes": [
"sha256:2219f1ebe88d266afa5516f993983eba8742b957fa4fd6854f3c73aa3030e931",
"sha256:c0d51f344b71656c2d395d2168600d91bea252a64fb5d503a955ea96426cde8b"
"sha256:64a8900b3a110e2d6ff4d87f4d8cd56f0c8527361d9fc9385fcb50efe7a4975a",
"sha256:8e9ff8006c41889ed8a11831dee62adf922e071f14d54c52946d1f7855ae7a8e"
],
"index": "pypi",
"version": "==1.17.20"
"version": "==1.17.26"
},
"botocore": {
"hashes": [
"sha256:80c32a81fb1ee8bdfa074a79bfb885bb2006e8a9782f2353c0c9f6392704e13a",
"sha256:e9e724b59278ebf5caf032be1e32bde0990d79e8052e3bbbb97b6c1d32feba28"
"sha256:4a785847a351e59f2329627fc9a19cf50f07644ea68996a1595d5a20487a423f"
],
"version": "==1.20.20"
"version": "==1.20.26"
},
"cachetools": {
"hashes": [
@ -217,10 +216,10 @@
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"version": "==3.0.4"
"version": "==4.0.0"
},
"click": {
"hashes": [
@ -304,11 +303,11 @@
},
"defusedxml": {
"hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
],
"index": "pypi",
"version": "==0.6.0"
"version": "==0.7.1"
},
"django": {
"hashes": [
@ -445,10 +444,10 @@
},
"google-auth": {
"hashes": [
"sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65",
"sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e"
"sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f",
"sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"
],
"version": "==1.27.0"
"version": "==1.27.1"
},
"gunicorn": {
"hashes": [
@ -817,10 +816,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
"sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a",
"sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"
],
"version": "==3.0.16"
"version": "==3.0.17"
},
"psycopg2-binary": {
"hashes": [
@ -1024,15 +1023,23 @@
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"index": "pypi",
"version": "==5.4.1"
@ -1069,10 +1076,47 @@
},
"ruamel.yaml": {
"hashes": [
"sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5",
"sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"
"sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3",
"sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"
],
"version": "==0.16.12"
"version": "==0.16.13"
},
"ruamel.yaml.clib": {
"hashes": [
"sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b",
"sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f",
"sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c",
"sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91",
"sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc",
"sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7",
"sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3",
"sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7",
"sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6",
"sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6",
"sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd",
"sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0",
"sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62",
"sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99",
"sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5",
"sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026",
"sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb",
"sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2",
"sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1",
"sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4",
"sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b",
"sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923",
"sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e",
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.10'",
"version": "==0.2.2"
},
"s3transfer": {
"hashes": [
@ -1765,15 +1809,23 @@
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"index": "pypi",
"version": "==5.4.1"

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.functions import ExtractHour
from django.utils.timezone import now
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.fields import SerializerMethodField
from drf_yasg2.utils import swagger_auto_schema, swagger_serializer_method
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
@ -37,23 +37,39 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
for hour in range(0, -24, -1):
results.append(
{
"x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
"y": data[hour * -1],
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple())
* 1000,
"y_cord": data[hour * -1],
}
)
return results
class AdministrationMetricsSerializer(Serializer):
class CoordinateSerializer(Serializer):
"""Coordinates for diagrams"""
x_cord = IntegerField(read_only=True)
y_cord = IntegerField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class LoginMetricsSerializer(Serializer):
"""Login Metrics per 1h"""
logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
@ -70,8 +86,8 @@ class AdministrationMetricsViewSet(ViewSet):
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)})
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
def list(self, request: Request) -> Response:
"""Login Metrics per 1h"""
serializer = AdministrationMetricsSerializer(True)
serializer = LoginMetricsSerializer(True)
return Response(serializer.data)

View file

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

View file

@ -1,4 +1,5 @@
"""api v2 urls"""
from django.conf import settings
from django.urls import path, re_path
from drf_yasg2 import openapi
from drf_yasg2.views import get_schema_view
@ -54,12 +55,24 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
from authentik.sources.oauth.api import OAuthSourceViewSet
from authentik.sources.saml.api import SAMLSourceViewSet
from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet
from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet
from authentik.stages.authenticator_static.api import (
AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
StaticDeviceViewSet,
)
from authentik.stages.authenticator_totp.api import (
AuthenticatorTOTPStageViewSet,
TOTPAdminDeviceViewSet,
TOTPDeviceViewSet,
)
from authentik.stages.authenticator_validate.api import (
AuthenticatorValidateStageViewSet,
)
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet
from authentik.stages.authenticator_webauthn.api import (
AuthenticateWebAuthnStageViewSet,
WebAuthnAdminDeviceViewSet,
WebAuthnDeviceViewSet,
)
from authentik.stages.captcha.api import CaptchaStageViewSet
from authentik.stages.consent.api import ConsentStageViewSet
from authentik.stages.deny.api import DenyStageViewSet
@ -133,6 +146,13 @@ router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register("propertymappings/scope", ScopeMappingViewSet)
router.register("authenticators/static", StaticDeviceViewSet)
router.register("authenticators/totp", TOTPDeviceViewSet)
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)
router.register("stages/all", StageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
@ -164,27 +184,26 @@ info = openapi.Info(
name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
),
)
SchemaView = get_schema_view(
info,
public=True,
permission_classes=(AllowAny,),
)
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
urlpatterns = [
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
path(
"swagger/",
SchemaView.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
urlpatterns = router.urls + [
path(
"flows/executor/<slug:flow_slug>/",
FlowExecutorView.as_view(),
name="flow-executor",
),
] + router.urls
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
]
if settings.DEBUG:
urlpatterns = urlpatterns + [
path(
"swagger/",
SchemaView.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,12 @@
"""NotificationTransport API Views"""
from django.http.response import Http404
from drf_yasg2.utils import no_body, swagger_auto_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.viewsets import ModelViewSet
from authentik.events.models import (
@ -38,12 +39,28 @@ class NotificationTransportSerializer(ModelSerializer):
]
class NotificationTransportTestSerializer(Serializer):
"""Notification test serializer"""
messages = ListField(child=CharField())
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class NotificationTransportViewSet(ModelViewSet):
"""NotificationTransport Viewset"""
queryset = NotificationTransport.objects.all()
serializer_class = NotificationTransportSerializer
@swagger_auto_schema(
responses={200: NotificationTransportTestSerializer(many=False)},
request_body=no_body,
)
@action(detail=True, methods=["post"])
# pylint: disable=invalid-name
def test(self, request: Request, pk=None) -> Response:
@ -61,6 +78,10 @@ class NotificationTransportViewSet(ModelViewSet):
user=request.user,
)
try:
return Response(transport.send(notification))
response = NotificationTransportTestSerializer(
data={"messages": transport.send(notification)}
)
response.is_valid()
return Response(response.data)
except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=503)

View file

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

View file

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

View file

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

View file

@ -9,11 +9,16 @@ from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import TemplateView, View
from drf_yasg2.utils import no_body, swagger_auto_schema
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import cleanse_dict
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
ChallengeTypes,
HttpChallengeResponse,
RedirectChallenge,
@ -40,9 +45,11 @@ SESSION_KEY_GET = "authentik_flows_get"
@method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(View):
class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views"""
permission_classes = [AllowAny]
flow: Flow
plan: Optional[FlowPlan] = None
@ -113,8 +120,13 @@ class FlowExecutorView(View):
self.current_stage_view.request = request
return super().dispatch(request)
@swagger_auto_schema(
responses={200: Challenge()},
request_body=no_body,
operation_id="flows_executor_get",
)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""pass get request to current stage"""
"""Get the next pending challenge from the currently active flow."""
self._logger.debug(
"f(exec): Passing GET",
view_class=class_to_path(self.current_stage_view.__class__),
@ -127,8 +139,13 @@ class FlowExecutorView(View):
self._logger.exception(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
@swagger_auto_schema(
responses={200: Challenge()},
request_body=ChallengeResponse(),
operation_id="flows_executor_solve",
)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""pass post request to current stage"""
"""Solve the previously retrieved challenge and advanced to the next stage."""
self._logger.debug(
"f(exec): Passing POST",
view_class=class_to_path(self.current_stage_view.__class__),
@ -175,8 +192,10 @@ class FlowExecutorView(View):
"f(exec): Continuing with next stage",
reamining=len(self.plan.stages),
)
kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug})
return redirect_with_qs(
"authentik_api:flow-executor", self.request.GET, **self.kwargs
"authentik_api:flow-executor", self.request.GET, **kwargs
)
# User passed all stages
self._logger.debug(

View file

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

View file

@ -55,13 +55,21 @@ class OutpostConsumer(AuthJsonConsumer):
OutpostState(
uid=self.channel_name, last_seen=datetime.now(), _outpost=self.outpost
).save(timeout=OUTPOST_HELLO_INTERVAL * 1.5)
LOGGER.debug("added channel to cache", channel_name=self.channel_name)
LOGGER.debug(
"added outpost instace to cache",
outpost=self.outpost,
channel_name=self.channel_name,
)
# pylint: disable=unused-argument
def disconnect(self, close_code):
if self.outpost:
OutpostState.for_channel(self.outpost, self.channel_name).delete()
LOGGER.debug("removed channel from cache", channel_name=self.channel_name)
LOGGER.debug(
"removed outpost instance from cache",
outpost=self.outpost,
channel_name=self.channel_name,
)
def receive_json(self, content: Data):
msg = from_dict(WebsocketMessage, content)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,8 @@
"""AuthenticatorStaticStage API Views"""
from rest_framework.viewsets import ModelViewSet
from django_otp.plugins.otp_static.models import StaticDevice
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
@ -19,3 +22,39 @@ class AuthenticatorStaticStageViewSet(ModelViewSet):
queryset = AuthenticatorStaticStage.objects.all()
serializer_class = AuthenticatorStaticStageSerializer
class StaticDeviceSerializer(ModelSerializer):
"""Serializer for static authenticator devices"""
class Meta:
model = StaticDevice
fields = ["name", "token_set"]
depth = 2
class StaticDeviceViewSet(ModelViewSet):
"""Viewset for static authenticator devices"""
queryset = StaticDevice.objects.none()
serializer_class = StaticDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
def get_queryset(self):
if not self.request:
return super().get_queryset()
return StaticDevice.objects.filter(user=self.request.user)
class StaticAdminDeviceViewSet(ReadOnlyModelViewSet):
"""Viewset for static authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = StaticDevice.objects.all()
serializer_class = StaticDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View file

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

View file

@ -1,5 +1,8 @@
"""AuthenticatorTOTPStage API Views"""
from rest_framework.viewsets import ModelViewSet
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
@ -19,3 +22,41 @@ class AuthenticatorTOTPStageViewSet(ModelViewSet):
queryset = AuthenticatorTOTPStage.objects.all()
serializer_class = AuthenticatorTOTPStageSerializer
class TOTPDeviceSerializer(ModelSerializer):
"""Serializer for totp authenticator devices"""
class Meta:
model = TOTPDevice
fields = [
"name",
]
depth = 2
class TOTPDeviceViewSet(ModelViewSet):
"""Viewset for totp authenticator devices"""
queryset = TOTPDevice.objects.none()
serializer_class = TOTPDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
def get_queryset(self):
if not self.request:
return super().get_queryset()
return TOTPDevice.objects.filter(user=self.request.user)
class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet):
"""Viewset for totp authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = TOTPDevice.objects.all()
serializer_class = TOTPDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View file

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

View file

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

View file

@ -1,8 +1,13 @@
"""AuthenticateWebAuthnStage API Views"""
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage,
WebAuthnDevice,
)
class AuthenticateWebAuthnStageSerializer(StageSerializer):
@ -19,3 +24,41 @@ class AuthenticateWebAuthnStageViewSet(ModelViewSet):
queryset = AuthenticateWebAuthnStage.objects.all()
serializer_class = AuthenticateWebAuthnStageSerializer
class WebAuthnDeviceSerializer(ModelSerializer):
"""Serializer for WebAuthn authenticator devices"""
class Meta:
model = WebAuthnDevice
fields = [
"name",
]
depth = 2
class WebAuthnDeviceViewSet(ModelViewSet):
"""Viewset for WebAuthn authenticator devices"""
queryset = WebAuthnDevice.objects.none()
serializer_class = WebAuthnDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
def get_queryset(self):
if not self.request:
return super().get_queryset()
return WebAuthnDevice.objects.filter(user=self.request.user)
class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet):
"""Viewset for WebAuthn authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = WebAuthnDevice.objects.all()
serializer_class = WebAuthnDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -279,6 +279,7 @@ stages:
displayName: Build static files for e2e
inputs:
script: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/src/api --additional-properties=typescriptThreePlus=true
cd web
npm i
npm run build

View file

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

View file

@ -4,7 +4,7 @@
|-----------------------------------|-------------------------|-------------|
| image.name | beryju/authentik | Image used to run the authentik server and worker |
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
| image.tag | 2021.3.3 | Image tag |
| image.tag | 2021.3.3 | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment |
@ -22,6 +22,10 @@
| config.email.use_ssl | false | Enable SSL |
| config.email.timeout | 10 | SMTP Timeout |
| config.email.from | authentik@localhost | Email address authentik will send from, should have a correct @domain |
| geoip.enabled | false | Optionally enable GeoIP |
| geoip.accountId | | GeoIP MaxMind Account ID |
| geoip.licenseKey | | GeoIP MaxMind License key |
| geoip.image | maxmindinc/geoipupdate:latest | GeoIP Updater image |
| backup.accessKey | | Optionally enable S3 Backup, Access Key |
| backup.secretKey | | Optionally enable S3 Backup, Secret Key |
| backup.bucket | | Optionally enable S3 Backup, Bucket |

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

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

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

View file

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

View file

@ -24,7 +24,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
github.com/recws-org/recws v1.2.1
github.com/sirupsen/logrus v1.8.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.5.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect

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

View file

@ -1,4 +1,4 @@
FROM golang:1.16.0 AS builder
FROM golang:1.16.2 AS builder
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 time import sleep
import yaml
from django.test import TestCase
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types.healthcheck import Healthcheck
from authentik import __version__
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.outposts.apps import AuthentikOutpostConfig
@ -93,3 +95,14 @@ class OutpostDockerTests(TestCase):
controller = DockerController(self.outpost, self.service_connection)
controller.up()
controller.down()
def test_docker_static(self):
"""test that deployment requires update"""
controller = DockerController(self.outpost, self.service_connection)
manifest = controller.get_static_deployment()
compose = yaml.load(manifest, Loader=yaml.SafeLoader)
self.assertEqual(compose["version"], "3.5")
self.assertEqual(
compose["services"]["authentik_proxy"]["image"],
f"beryju/authentik-proxy:{__version__}",
)

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
class TestControllers(TestCase):
class TestProxyKubernetes(TestCase):
"""Test Controllers"""
def setUp(self):

View file

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

View file

@ -10,6 +10,25 @@ variables:
branchName: ${{ replace(variables['Build.SourceBranchName'], 'refs/heads/', '') }}
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
jobs:
- job: eslint
@ -20,6 +39,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -37,6 +61,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -56,6 +85,11 @@ stages:
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Npm@1
inputs:
command: 'install'
@ -71,16 +105,21 @@ stages:
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
containerRegistry: 'beryjuorg-harbor'
repository: 'authentik/static'
command: 'buildAndPush'
Dockerfile: 'web/Dockerfile'
tags: "gh-$(branchName)"
buildContext: 'web/'
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/src/api/"
- task: Bash@3
inputs:
targetType: 'inline'
script: |
python ./scripts/az_do_set_branch.py
- task: Docker@2
inputs:
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": {
"version": "4.87.3",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.87.3.tgz",
"integrity": "sha512-hDNMPa7B1zKD8LWFZO4SS5hC/N+yvuci2sAn8HJd+EIbAvbMAUkRsyZ0/XO3BG3RVtpSlgq7q8x1pAHC/FTFuA=="
"version": "4.90.5",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.90.5.tgz",
"integrity": "sha512-Fe0C8UkzSjtacQ+fHXlFB/LHzrv/c2K4z479C6dboOgkGQE1FyB0wt1NBfxij0D++rhOy04OOYdE+Tr0JSlZKw=="
},
"@rollup/plugin-typescript": {
"version": "8.2.0",
@ -143,27 +143,27 @@
}
},
"@sentry/browser": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.1.tgz",
"integrity": "sha512-OAikFZ9EimD3noxMp8tA6Cf6qJcQ2U8k5QSgTPwdx+09nZOGJzbRFteK7WWmrS93ZJdzN61lpSQbg5v+bmmfbQ==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.2.tgz",
"integrity": "sha512-K5UGyEePtVPZIFMoiRafhd4Ov0M1kdozVsVKIPZrOpJyjQdPNX+fYDNL/h0nVmgOlE2S/uu4fl4mEfe/6aLShw==",
"requires": {
"@sentry/core": "6.2.1",
"@sentry/types": "6.2.1",
"@sentry/utils": "6.2.1",
"@sentry/core": "6.2.2",
"@sentry/types": "6.2.2",
"@sentry/utils": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
"@sentry/types": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
},
"@sentry/utils": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": {
"@sentry/types": "6.2.1",
"@sentry/types": "6.2.2",
"tslib": "^1.9.3"
}
},
@ -175,48 +175,48 @@
}
},
"@sentry/core": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.1.tgz",
"integrity": "sha512-jPqQEtafxxDtLONhCbTHh/Uq8mZRhsfbwJTSVYfPVEe/ELfFZLQK7tP6rOh7zEWKbTkE0mE6XcaoH3ZRAhgrqg==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.2.tgz",
"integrity": "sha512-qqWbvvXtymfXh7N5eEvk97MCnMURuyFIgqWdVD4MQM6yIfDCy36CyGfuQ3ViHTLZGdIfEOhLL9/f4kzf1RzqBA==",
"requires": {
"@sentry/hub": "6.2.1",
"@sentry/minimal": "6.2.1",
"@sentry/types": "6.2.1",
"@sentry/utils": "6.2.1",
"@sentry/hub": "6.2.2",
"@sentry/minimal": "6.2.2",
"@sentry/types": "6.2.2",
"@sentry/utils": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
"@sentry/hub": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz",
"integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
"integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
"requires": {
"@sentry/types": "6.2.1",
"@sentry/utils": "6.2.1",
"@sentry/types": "6.2.2",
"@sentry/utils": "6.2.2",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz",
"integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
"integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
"requires": {
"@sentry/hub": "6.2.1",
"@sentry/types": "6.2.1",
"@sentry/hub": "6.2.2",
"@sentry/types": "6.2.2",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
},
"@sentry/utils": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": {
"@sentry/types": "6.2.1",
"@sentry/types": "6.2.2",
"tslib": "^1.9.3"
}
},
@ -228,12 +228,12 @@
}
},
"@sentry/hub": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.1.tgz",
"integrity": "sha512-pG7wCQeRpzeP6t0bT4T0X029R19dbDS3/qswF8BL6bg0AI3afjfjBAZm/fqn1Uwe/uBoMHVVdbxgJDZeQ5d4rQ==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz",
"integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==",
"requires": {
"@sentry/types": "6.2.1",
"@sentry/utils": "6.2.1",
"@sentry/types": "6.2.2",
"@sentry/utils": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
@ -245,12 +245,12 @@
}
},
"@sentry/minimal": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.1.tgz",
"integrity": "sha512-wuSXB4Ayxv9rBEQ4pm7fnG4UU2ZPtPnnChoEfd4/mw1UthXSvmPFEn6O4pdo2G8fTkl8eqm6wT/Q7uIXMEmw+A==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz",
"integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==",
"requires": {
"@sentry/hub": "6.2.1",
"@sentry/types": "6.2.1",
"@sentry/hub": "6.2.2",
"@sentry/types": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
@ -262,14 +262,14 @@
}
},
"@sentry/tracing": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.1.tgz",
"integrity": "sha512-bvStY1SnL08wkSeVK3j9K5rivQQJdKFCPR2VYRFOCaUoleZ6ChPUnBvxQ/E2LXc0hk/y/wo1q4r5B0dfCCY+bQ==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.2.tgz",
"integrity": "sha512-mAkPoqtofNfka/u9rOVVDQPaEoTmr0AQh654g9ZqsaqsOJLKjB4FDLVNubWs90fjeKqHiYkI3ZHPak2TzHBPkw==",
"requires": {
"@sentry/hub": "6.2.1",
"@sentry/minimal": "6.2.1",
"@sentry/types": "6.2.1",
"@sentry/utils": "6.2.1",
"@sentry/hub": "6.2.2",
"@sentry/minimal": "6.2.2",
"@sentry/types": "6.2.2",
"@sentry/utils": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
@ -281,16 +281,16 @@
}
},
"@sentry/types": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.1.tgz",
"integrity": "sha512-h0OV1QT+fv5ojfK5/+iEXClu33HirmvbjcQC2jf05IHj9yXIOWy6EB10S8nBjuLiiFqQiAQYj3FN9Ip4eN8NJA=="
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz",
"integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ=="
},
"@sentry/utils": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.1.tgz",
"integrity": "sha512-6kQgM/yBPdXu+3qbJnI6HBcWztN9QfiMkH++ZiKk4ERhg9d2LYWlze478uTU5Fyo/JQYcp+McpjtjpR9QIrr0g==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz",
"integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==",
"requires": {
"@sentry/types": "6.2.1",
"@sentry/types": "6.2.2",
"tslib": "^1.9.3"
},
"dependencies": {
@ -310,12 +310,13 @@
}
},
"@types/clean-css": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.2.tgz",
"integrity": "sha512-xiTJn3bmDh1lA8c6iVJs4ZhHw+pcmxXlJQXOB6G1oULaak8rmarIeFKI4aTJ7849dEhaO612wgIualZfbxTJwA==",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.3.tgz",
"integrity": "sha512-ET0ldU/vpXecy5vO8JRIhtJWSrk1vzXdJcp3Bjf8bARZynl6vfkhEKY/A7njfNIRlmyTGuVFuqnD6I3tOGdXpQ==",
"dev": true,
"requires": {
"@types/node": "*"
"@types/node": "*",
"source-map": "^0.6.0"
}
},
"@types/codemirror": {
@ -404,22 +405,22 @@
}
},
"@types/uglify-js": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.0.tgz",
"integrity": "sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q==",
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz",
"integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.16.1.tgz",
"integrity": "sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.17.0.tgz",
"integrity": "sha512-/fKFDcoHg8oNan39IKFOb5WmV7oWhQe1K6CDaAVfJaNWEhmfqlA24g+u1lqU5bMH7zuNasfMId4LaYWC5ijRLw==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "4.16.1",
"@typescript-eslint/scope-manager": "4.16.1",
"@typescript-eslint/experimental-utils": "4.17.0",
"@typescript-eslint/scope-manager": "4.17.0",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15",
@ -429,112 +430,55 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz",
"integrity": "sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.17.0.tgz",
"integrity": "sha512-ZR2NIUbnIBj+LGqCFGQ9yk2EBQrpVVFOh9/Kd0Lm6gLpSAcCuLLe5lUCibKGCqyH9HPwYC0GIJce2O1i8VYmWA==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.16.1",
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/typescript-estree": "4.16.1",
"@typescript-eslint/scope-manager": "4.17.0",
"@typescript-eslint/types": "4.17.0",
"@typescript-eslint/typescript-estree": "4.17.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz",
"integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.17.0.tgz",
"integrity": "sha512-KYdksiZQ0N1t+6qpnl6JeK9ycCFprS9xBAiIrw4gSphqONt8wydBw4BXJi3C11ywZmyHulvMaLjWsxDjUSDwAw==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "4.16.1",
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/typescript-estree": "4.16.1",
"@typescript-eslint/scope-manager": "4.17.0",
"@typescript-eslint/types": "4.17.0",
"@typescript-eslint/typescript-estree": "4.17.0",
"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": {
"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==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.17.0.tgz",
"integrity": "sha512-OJ+CeTliuW+UZ9qgULrnGpPQ1bhrZNFpfT/Bc0pzNeyZwMik7/ykJ0JHnQ7krHanFN9wcnPK89pwn84cRUmYjw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1"
"@typescript-eslint/types": "4.17.0",
"@typescript-eslint/visitor-keys": "4.17.0"
}
},
"@typescript-eslint/types": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz",
"integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.17.0.tgz",
"integrity": "sha512-RN5z8qYpJ+kXwnLlyzZkiJwfW2AY458Bf8WqllkondQIcN2ZxQowAToGSd9BlAUZDB5Ea8I6mqL2quGYCLT+2g==",
"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==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.17.0.tgz",
"integrity": "sha512-lRhSFIZKUEPPWpWfwuZBH9trYIEJSI0vYsrxbvVvNyIUDoKWaklOAelsSkeh3E2VBSZiNe9BZ4E5tYBZbUczVQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1",
"@typescript-eslint/types": "4.17.0",
"@typescript-eslint/visitor-keys": "4.17.0",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
@ -559,12 +503,12 @@
}
},
"@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==",
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.17.0.tgz",
"integrity": "sha512-WfuMN8mm5SSqXuAr9NM+fItJ0SVVphobWYkWOwQ1odsfC014Vdxk/92t4JwS1Q6fCA/ABfCKpa3AVtpUKTNKGQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/types": "4.17.0",
"eslint-visitor-keys": "^2.0.0"
}
},
@ -2280,16 +2224,16 @@
}
},
"minify-html-literals": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.2.tgz",
"integrity": "sha512-DBdi0md84vjvwmLoo9xleFV5FkhzOwfKBqcmoVFL54c9CFlSBtG9KTKEQqiwscB+acewculqys1cDnwyrYlNtg==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz",
"integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==",
"dev": true,
"requires": {
"@types/html-minifier": "^3.5.3",
"clean-css": "^4.2.1",
"html-minifier": "^4.0.0",
"magic-string": "^0.25.0",
"parse-literals": "^1.2.0"
"parse-literals": "^1.2.1"
}
},
"minimatch": {
@ -2489,20 +2433,12 @@
}
},
"parse-literals": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.0.tgz",
"integrity": "sha512-gh4zPwvFSXx9ginX8lu9MP3OPHN3VV12PXI8IXD6oMCklFqM82pfbU9e/PKf9r7oLpbqlDSDyHYSVlxxuq3Iew==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz",
"integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==",
"dev": true,
"requires": {
"typescript": "^2.9.2 || ^3.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
}
"typescript": "^2.9.2 || ^3.0.0 || ^4.0.0"
}
},
"parse5": {
@ -2717,9 +2653,9 @@
}
},
"rollup": {
"version": "2.40.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.40.0.tgz",
"integrity": "sha512-WiOGAPbXoHu+TOz6hyYUxIksOwsY/21TRWoO593jgYt8mvYafYqQl+axaA8y1z2HFazNUUrsMSjahV2A6/2R9A==",
"version": "2.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.41.1.tgz",
"integrity": "sha512-nepLFAW5W71/MWpS2Yr7r31eS7HRfYg2RXnxb6ehqN9zY42yACxKtEfb4xq8SmNfUohAzGMcyl6jkwdLOAiUbg==",
"requires": {
"fsevents": "~2.3.1"
}
@ -2801,12 +2737,12 @@
}
},
"rollup-plugin-minify-html-literals": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.5.tgz",
"integrity": "sha512-x4FzCnbBpYdme7MQDS3+18CvYLqakAtM/JmA3hqXplwzMeZWW3l14KU7H33RhJlHH8Klgv49hGtBRLWLfjCudw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals/-/rollup-plugin-minify-html-literals-1.2.6.tgz",
"integrity": "sha512-JRq2fjlCTiw0zu+1Sy3ClHGCxA79dWGr4HLHWSQgd060StVW9fBVksuj8Xw/suPkNSGClJf/4xNQ1MF6JeXPaw==",
"dev": true,
"requires": {
"minify-html-literals": "^1.3.2",
"minify-html-literals": "^1.3.5",
"rollup-pluginutils": "^2.8.2"
}
},
@ -3383,15 +3319,15 @@
"dev": true
},
"typescript": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz",
"integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
"dev": true
},
"uglify-js": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.0.tgz",
"integrity": "sha512-e1KQFRCpOxnrJsJVqDUCjURq+wXvIn7cK2sRAx9XL3HYLL9aezOP4Pb1+Y3/o693EPk111Yj2Q+IUXxcpHlygQ==",
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.0.tgz",
"integrity": "sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==",
"dev": true
},
"union-value": {

View file

@ -11,9 +11,9 @@
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"@patternfly/patternfly": "^4.87.3",
"@sentry/browser": "^6.2.1",
"@sentry/tracing": "^6.2.1",
"@patternfly/patternfly": "^4.90.5",
"@sentry/browser": "^6.2.2",
"@sentry/tracing": "^6.2.2",
"@types/chart.js": "^2.9.31",
"@types/codemirror": "0.0.108",
"@types/grecaptcha": "^3.0.1",
@ -24,7 +24,7 @@
"flowchart.js": "^1.15.0",
"lit-element": "^2.4.0",
"lit-html": "^1.3.0",
"rollup": "^2.40.0",
"rollup": "^2.41.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2",
"rollup-plugin-external-globals": "^0.6.1",
@ -33,17 +33,17 @@
},
"devDependencies": {
"@rollup/plugin-typescript": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"eslint": "^7.21.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-lit": "^1.3.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-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2",
"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 {
page?: number;
page_size?: number;
@ -13,104 +6,27 @@ export interface QueryArguments {
export interface BaseInheritanceModel {
object_type: string;
objectType: string;
verbose_name: string;
verbose_name_plural: string;
verboseName: string;
verboseNamePlural: string;
}
export class Client {
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 {
export interface AKPagination {
next?: number;
previous?: number;
count: number;
current: number;
total_pages: number;
totalPages: number;
start_index: number;
end_index: number;
startIndex: number;
endIndex: number;
}
export interface AKResponse<T> {
pagination: PBPagination;
pagination: AKPagination;
results: Array<T>;
}

View file

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

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 {
pk: number;
@ -11,37 +11,7 @@ export interface EventContext {
[key: string]: EventContext | string | number | string[];
}
export class Event {
pk: string;
export interface EventWithContext extends Event {
user: EventUser;
action: string;
app: string;
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 { TypeCreate } from "./Providers";
export enum ChallengeTypes {
native = "native",
response = "response",
shell = "shell",
redirect = "redirect",
}
import { ChallengeTypeEnum } from "./models";
export interface Error {
code: string;
@ -18,11 +10,12 @@ export interface ErrorDict {
}
export interface Challenge {
type: ChallengeTypes;
type: ChallengeTypeEnum;
component?: string;
title?: string;
response_errors?: ErrorDict;
}
export interface WithUserInfoChallenge extends Challenge {
pending_user: string;
pending_user_avatar: string;
@ -31,6 +24,7 @@ export interface WithUserInfoChallenge extends Challenge {
export interface ShellChallenge extends Challenge {
body: string;
}
export interface RedirectChallenge extends Challenge {
to: string;
}
@ -44,104 +38,3 @@ export enum FlowDesignation {
Recovery = "recovery",
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>;
export class User {
pk: number;
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;
});
export function me(): Promise<User> {
if (!_globalMePromise) {
_globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe({});
}
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;
}
html > input {
position: absolute;
top: -2000px;
left: -2000px;
}
.pf-c-page__header {
z-index: 0;
}
@ -83,7 +89,7 @@ select[multiple] {
/* ensure background on non-flow pages match */
.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;
}
@ -157,7 +163,7 @@ ak-message {
color: var(--ak-dark-foreground) !important;
}
/* tabs, vertical */
.pf-c-tabs__link {
.pf-c-tabs.pf-m-vertical .pf-c-tabs__link {
background-color: var(--ak-dark-background-light);
}
/* table, on mobile */

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