Merge branch 'master' into version-2021.12
This commit is contained in:
commit
fbb6756488
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
|
@ -7,6 +7,7 @@ exemptLabels:
|
|||
- pinned
|
||||
- security
|
||||
- pr_wanted
|
||||
- enhancement/confirmed
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
|
|
1
.python-version
Normal file
1
.python-version
Normal file
|
@ -0,0 +1 @@
|
|||
3.9.7
|
|
@ -58,8 +58,6 @@ RUN apt-get update && \
|
|||
curl ca-certificates gnupg git runit libpq-dev \
|
||||
postgresql-client build-essential libxmlsec1-dev \
|
||||
pkg-config libmaxminddb0 && \
|
||||
pip install lxml==4.6.4 --no-cache-dir && \
|
||||
export C_INCLUDE_PATH=/usr/local/lib/python3.10/site-packages/lxml/includes && \
|
||||
pip install -r /requirements.txt --no-cache-dir && \
|
||||
apt-get remove --purge -y build-essential git && \
|
||||
apt-get autoremove --purge -y && \
|
||||
|
|
2
Makefile
2
Makefile
|
@ -4,7 +4,7 @@ UID = $(shell id -u)
|
|||
GID = $(shell id -g)
|
||||
NPM_VERSION = $(shell python -m scripts.npm_version)
|
||||
|
||||
all: lint-fix lint test gen
|
||||
all: lint-fix lint test gen web
|
||||
|
||||
test-integration:
|
||||
coverage run manage.py test tests/integration
|
||||
|
|
5
Pipfile
5
Pipfile
|
@ -32,15 +32,14 @@ geoip2 = "*"
|
|||
gunicorn = "*"
|
||||
kubernetes = "==v19.15.0"
|
||||
ldap3 = "*"
|
||||
# 4.7.0 and later remove `lxml-version.h` which is required by xmlsec
|
||||
lxml = "==4.6.5"
|
||||
lxml = "*"
|
||||
packaging = "*"
|
||||
psycopg2-binary = "*"
|
||||
pycryptodome = "*"
|
||||
pyjwt = "*"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
sentry-sdk = "*"
|
||||
sentry-sdk = { git = 'https://github.com/beryju/sentry-python.git', ref = '379aee28b15d3b87b381317746c4efd24b3d7bc3' }
|
||||
service_identity = "*"
|
||||
structlog = "*"
|
||||
swagger-spec-validator = "*"
|
||||
|
|
156
Pipfile.lock
generated
156
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "6a89870496296af32dbc2f64b0832d4c20010829ada0b3c4dc27fee56b68fad9"
|
||||
"sha256": "dedb51159ef09fd9b00ab28022706f525c9df057ffd646e2a552784341a10538"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
|
@ -169,19 +169,19 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:76b3ee0d1dd860c9218bc864cd29f1ee986f6e1e75e8669725dd3c411039379e",
|
||||
"sha256:c39cb6ed376ba1d4689ac8f6759a2b2d8a0b0424dbec0cd3af1558079bcf06e8"
|
||||
"sha256:739705b28e6b2329ea3b481ba801d439c296aaf176f7850729147ba99bbf8a9a",
|
||||
"sha256:8f08e8e94bf107c5e9866684e9aadf8d9f60abed0cfe5c1dba4e7328674a1986"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.20.23"
|
||||
"version": "==1.20.24"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:640b62110aa6d1c25553eceafb5bcd89aedeb84b191598d1f6492ad24374d285",
|
||||
"sha256:7459766c4594f3b8877e8013f93f0dc6c6486acbeb7d9c9ae488396529cc2e84"
|
||||
"sha256:43006b4f52d7bb655319d3da0f615cdbee7762853acc1ebcb1d49f962e6b4806",
|
||||
"sha256:e78d48c50c8c013fb9b362c6202fece2fe868edfd89b51968080180bdff41617"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.23.23"
|
||||
"version": "==1.23.24"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
|
@ -196,7 +196,7 @@
|
|||
"sha256:1ef33f089e0a494e8d1b487508356f055c865b1955b125c00c991a4358543c80",
|
||||
"sha256:8eca49962b1bfc09c24d442aa55688be88efe5c24aeef89d3be135614b95c678"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_version < '4'",
|
||||
"markers": "python_version >= '3.7' and python_full_version < '4.0.0'",
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"cbor2": {
|
||||
|
@ -325,7 +325,7 @@
|
|||
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
|
||||
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
|
||||
],
|
||||
"markers": "python_version < '4' and python_full_version >= '3.6.2'",
|
||||
"markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'",
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"click-plugins": {
|
||||
|
@ -494,11 +494,11 @@
|
|||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf",
|
||||
"sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"
|
||||
"sha256:48e64f08244fa0df9e2b8fbd405edec263d8e1251112a06d0073b546b7c86b9c",
|
||||
"sha256:8b987d5683f5b3553dd946d4972048d3117fc526cb0bc01a3f021e81af53f39e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.12.4"
|
||||
"version": "==3.13.0"
|
||||
},
|
||||
"djangorestframework-guardian": {
|
||||
"hashes": [
|
||||
|
@ -816,69 +816,69 @@
|
|||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:11ae552a78612620afd15625be9f1b82e3cc2e634f90d6b11709b10a100cba59",
|
||||
"sha256:121fc6f71c692b49af6c963b84ab7084402624ffbe605287da362f8af0668ea3",
|
||||
"sha256:124f09614f999551ac65e5b9875981ce4b66ac4b8e2ba9284572f741935df3d9",
|
||||
"sha256:12ae2339d32a2b15010972e1e2467345b7bf962e155671239fba74c229564b7f",
|
||||
"sha256:12d8d6fe3ddef629ac1349fa89a638b296a34b6529573f5055d1cb4e5245f73b",
|
||||
"sha256:1a2a7659b8eb93c6daee350a0d844994d49245a0f6c05c747f619386fb90ba04",
|
||||
"sha256:1ccbfe5d17835db906f2bab6f15b34194db1a5b07929cba3cf45a96dbfbfefc0",
|
||||
"sha256:2f77556266a8fe5428b8759fbfc4bd70be1d1d9c9b25d2a414f6a0c0b0f09120",
|
||||
"sha256:3534d7c468c044f6aef3c0aff541db2826986a29ea73f2ca831f5d5284d9b570",
|
||||
"sha256:3884476a90d415be79adfa4e0e393048630d0d5bcd5757c4c07d8b4b00a1096b",
|
||||
"sha256:3b95fb7e6f9c2f53db88f4642231fc2b8907d854e614710996a96f1f32018d5c",
|
||||
"sha256:46515773570a33eae13e451c8fcf440222ef24bd3b26f40774dd0bd8b6db15b2",
|
||||
"sha256:46f21f2600d001af10e847df9eb3b832e8a439f696c04891bcb8a8cedd859af9",
|
||||
"sha256:473701599665d874919d05bb33b56180447b3a9da8d52d6d9799f381ce23f95c",
|
||||
"sha256:4b9390bf973e3907d967b75be199cf1978ca8443183cf1e78ad80ad8be9cf242",
|
||||
"sha256:4f415624cf8b065796649a5e4621773dc5c9ea574a944c76a7f8a6d3d2906b41",
|
||||
"sha256:534032a5ceb34bba1da193b7d386ac575127cc39338379f39a164b10d97ade89",
|
||||
"sha256:558485218ee06458643b929765ac1eb04519ca3d1e2dcc288517de864c747c33",
|
||||
"sha256:57cf05466917e08f90e323f025b96f493f92c0344694f5702579ab4b7e2eb10d",
|
||||
"sha256:59d77bfa3bea13caee95bc0d3f1c518b15049b97dd61ea8b3d71ce677a67f808",
|
||||
"sha256:5d5254c815c186744c8f922e2ce861a2bdeabc06520b4b30b2f7d9767791ce6e",
|
||||
"sha256:5ea121cb66d7e5cb396b4c3ca90471252b94e01809805cfe3e4e44be2db3a99c",
|
||||
"sha256:60aeb14ff9022d2687ef98ce55f6342944c40d00916452bb90899a191802137a",
|
||||
"sha256:642eb4cabd997c9b949a994f9643cd8ae00cf4ca8c5cd9c273962296fadf1c44",
|
||||
"sha256:6548fc551de15f310dd0564751d9dc3d405278d45ea9b2b369ed1eccf142e1f5",
|
||||
"sha256:68a851176c931e2b3de6214347b767451243eeed3bea34c172127bbb5bf6c210",
|
||||
"sha256:6e84edecc3a82f90d44ddee2ee2a2630d4994b8471816e226d2b771cda7ac4ca",
|
||||
"sha256:73e8614258404b2689a26cb5d002512b8bc4dfa18aca86382f68f959aee9b0c8",
|
||||
"sha256:7679bb6e4d9a3978a46ab19a3560e8d2b7265ef3c88152e7fdc130d649789887",
|
||||
"sha256:76b6c296e4f7a1a8a128aec42d128646897f9ae9a700ef6839cdc9b3900db9b5",
|
||||
"sha256:7f00cc64b49d2ef19ddae898a3def9dd8fda9c3d27c8a174c2889ee757918e71",
|
||||
"sha256:8021eeff7fabde21b9858ed058a8250ad230cede91764d598c2466b0ba70db8b",
|
||||
"sha256:87f8f7df70b90fbe7b49969f07b347e3f978f8bd1046bb8ecae659921869202b",
|
||||
"sha256:916d457ad84e05b7db52700bad0a15c56e0c3000dcaf1263b2fb7a56fe148996",
|
||||
"sha256:925174cafb0f1179a7fd38da90302555d7445e34c9ece68019e53c946be7f542",
|
||||
"sha256:9801bcd52ac9c795a7d81ea67471a42cffe532e46cfb750cd5713befc5c019c0",
|
||||
"sha256:99cf827f5a783038eb313beee6533dddb8bdb086d7269c5c144c1c952d142ace",
|
||||
"sha256:a21b78af7e2e13bec6bea12fc33bc05730197674f3e5402ce214d07026ccfebd",
|
||||
"sha256:a52e8f317336a44836475e9c802f51c2dc38d612eaa76532cb1d17690338b63b",
|
||||
"sha256:a702005e447d712375433ed0499cb6e1503fadd6c96a47f51d707b4d37b76d3c",
|
||||
"sha256:a708c291900c40a7ecf23f1d2384ed0bc0604e24094dd13417c7e7f8f7a50d93",
|
||||
"sha256:a7790a273225b0c46e5f859c1327f0f659896cc72eaa537d23aa3ad9ff2a1cc1",
|
||||
"sha256:abcf7daa5ebcc89328326254f6dd6d566adb483d4d00178892afd386ab389de2",
|
||||
"sha256:add017c5bd6b9ec3a5f09248396b6ee2ce61c5621f087eb2269c813cd8813808",
|
||||
"sha256:af4139172ff0263d269abdcc641e944c9de4b5d660894a3ec7e9f9db63b56ac9",
|
||||
"sha256:b4015baed99d046c760f09a4c59d234d8f398a454380c3cf0b859aba97136090",
|
||||
"sha256:ba0006799f21d83c3717fe20e2707a10bbc296475155aadf4f5850f6659b96b9",
|
||||
"sha256:bdb98f4c9e8a1735efddfaa995b0c96559792da15d56b76428bdfc29f77c4cdb",
|
||||
"sha256:c34234a1bc9e466c104372af74d11a9f98338a3f72fae22b80485171a64e0144",
|
||||
"sha256:c580c2a61d8297a6e47f4d01f066517dbb019be98032880d19ece7f337a9401d",
|
||||
"sha256:ca9a40497f7e97a2a961c04fa8a6f23d790b0521350a8b455759d786b0bcb203",
|
||||
"sha256:cab343b265e38d4e00649cbbad9278b734c5715f9bcbb72c85a1f99b1a58e19a",
|
||||
"sha256:ce52aad32ec6e46d1a91ff8b8014a91538800dd533914bfc4a82f5018d971408",
|
||||
"sha256:da07c7e7fc9a3f40446b78c54dbba8bfd5c9100dfecb21b65bfe3f57844f5e71",
|
||||
"sha256:dc8a0dbb2a10ae8bb609584f5c504789f0f3d0d81840da4849102ec84289f952",
|
||||
"sha256:e5b4b0d9440046ead3bd425eb2b852499241ee0cef1ae151038e4f87ede888c4",
|
||||
"sha256:f33d8efb42e4fc2b31b3b4527940b25cdebb3026fb56a80c1c1c11a4271d2352",
|
||||
"sha256:f6befb83bca720b71d6bd6326a3b26e9496ae6649e26585de024890fe50f49b8",
|
||||
"sha256:fcc849b28f584ed1dbf277291ded5c32bb3476a37032df4a1d523b55faa5f944",
|
||||
"sha256:ff44de36772b05c2eb74f2b4b6d1ae29b8f41ed5506310ce1258d44826ee38c1"
|
||||
"sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4",
|
||||
"sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f",
|
||||
"sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a",
|
||||
"sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944",
|
||||
"sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1",
|
||||
"sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d",
|
||||
"sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d",
|
||||
"sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e",
|
||||
"sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d",
|
||||
"sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a",
|
||||
"sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675",
|
||||
"sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3",
|
||||
"sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55",
|
||||
"sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60",
|
||||
"sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d",
|
||||
"sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6",
|
||||
"sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e",
|
||||
"sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5",
|
||||
"sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5",
|
||||
"sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42",
|
||||
"sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0",
|
||||
"sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d",
|
||||
"sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489",
|
||||
"sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440",
|
||||
"sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e",
|
||||
"sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6",
|
||||
"sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e",
|
||||
"sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f",
|
||||
"sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d",
|
||||
"sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03",
|
||||
"sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9",
|
||||
"sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9",
|
||||
"sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd",
|
||||
"sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6",
|
||||
"sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4",
|
||||
"sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868",
|
||||
"sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267",
|
||||
"sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2",
|
||||
"sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4",
|
||||
"sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24",
|
||||
"sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2",
|
||||
"sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db",
|
||||
"sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a",
|
||||
"sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8",
|
||||
"sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175",
|
||||
"sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851",
|
||||
"sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b",
|
||||
"sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e",
|
||||
"sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986",
|
||||
"sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f",
|
||||
"sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419",
|
||||
"sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7",
|
||||
"sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7",
|
||||
"sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36",
|
||||
"sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc",
|
||||
"sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b",
|
||||
"sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e",
|
||||
"sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17",
|
||||
"sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3",
|
||||
"sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.6.5"
|
||||
"version": "==4.7.1"
|
||||
},
|
||||
"maxminddb": {
|
||||
"hashes": [
|
||||
|
@ -1312,12 +1312,12 @@
|
|||
"version": "==0.5.0"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"git": "https://github.com/beryju/sentry-python.git",
|
||||
"hashes": [
|
||||
"sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6",
|
||||
"sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.0"
|
||||
"ref": "379aee28b15d3b87b381317746c4efd24b3d7bc3"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
|
@ -2387,11 +2387,11 @@
|
|||
},
|
||||
"tomli": {
|
||||
"hashes": [
|
||||
"sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee",
|
||||
"sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"
|
||||
"sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f",
|
||||
"sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.2.2"
|
||||
"version": "==1.2.3"
|
||||
},
|
||||
"trio": {
|
||||
"hashes": [
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
"""authentik administration metrics"""
|
||||
import time
|
||||
from collections import Counter
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db.models import Count, ExpressionWrapper, F
|
||||
from django.db.models.fields import DurationField
|
||||
from django.db.models.functions import ExtractHour
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_field
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.fields import IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.request import Request
|
||||
|
@ -15,31 +8,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
||||
def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
|
||||
"""Get event count by hour in the last day, fill with zeros"""
|
||||
date_from = now() - timedelta(days=1)
|
||||
result = (
|
||||
Event.objects.filter(created__gte=date_from, **filter_kwargs)
|
||||
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
|
||||
.annotate(age_hours=ExtractHour("age"))
|
||||
.values("age_hours")
|
||||
.annotate(count=Count("pk"))
|
||||
.order_by("age_hours")
|
||||
)
|
||||
data = Counter({int(d["age_hours"]): d["count"] for d in result})
|
||||
results = []
|
||||
_now = now()
|
||||
for hour in range(0, -24, -1):
|
||||
results.append(
|
||||
{
|
||||
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
|
||||
"y_cord": data[hour * -1],
|
||||
}
|
||||
)
|
||||
return results
|
||||
from authentik.events.models import EventAction
|
||||
|
||||
|
||||
class CoordinateSerializer(PassiveSerializer):
|
||||
|
@ -58,12 +27,22 @@ class LoginMetricsSerializer(PassiveSerializer):
|
|||
@extend_schema_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)
|
||||
user = self.context["user"]
|
||||
return (
|
||||
get_objects_for_user(user, "authentik_events.view_event")
|
||||
.filter(action=EventAction.LOGIN)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
||||
@extend_schema_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)
|
||||
user = self.context["user"]
|
||||
return (
|
||||
get_objects_for_user(user, "authentik_events.view_event")
|
||||
.filter(action=EventAction.LOGIN_FAILED)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
||||
|
||||
class AdministrationMetricsViewSet(APIView):
|
||||
|
@ -75,4 +54,5 @@ class AdministrationMetricsViewSet(APIView):
|
|||
def get(self, request: Request) -> Response:
|
||||
"""Login Metrics per 1h"""
|
||||
serializer = LoginMetricsSerializer(True)
|
||||
serializer.context["user"] = request.user
|
||||
return Response(serializer.data)
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.http.response import HttpResponseBadRequest
|
|||
from django.shortcuts import get_object_or_404
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import ReadOnlyField
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
|
@ -15,7 +16,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 CoordinateSerializer, get_events_per_1h
|
||||
from authentik.admin.api.metrics import CoordinateSerializer
|
||||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
|
@ -239,8 +240,10 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
|||
"""Metrics for application logins"""
|
||||
app = self.get_object()
|
||||
return Response(
|
||||
get_events_per_1h(
|
||||
get_objects_for_user(request.user, "authentik_events.view_event")
|
||||
.filter(
|
||||
action=EventAction.AUTHORIZE_APPLICATION,
|
||||
context__authorized_application__pk=app.pk.hex,
|
||||
)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
|
|
@ -104,14 +104,14 @@ class SourceViewSet(
|
|||
)
|
||||
matching_sources: list[UserSettingSerializer] = []
|
||||
for source in _all_sources:
|
||||
user_settings = source.ui_user_settings
|
||||
user_settings = source.ui_user_settings()
|
||||
if not user_settings:
|
||||
continue
|
||||
policy_engine = PolicyEngine(source, request.user, request)
|
||||
policy_engine.build()
|
||||
if not policy_engine.passing:
|
||||
continue
|
||||
source_settings = source.ui_user_settings
|
||||
source_settings = source.ui_user_settings()
|
||||
source_settings.initial_data["object_uid"] = source.slug
|
||||
if not source_settings.is_valid():
|
||||
LOGGER.warning(source_settings.errors)
|
||||
|
|
|
@ -38,7 +38,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 CoordinateSerializer, get_events_per_1h
|
||||
from authentik.admin.api.metrics import CoordinateSerializer
|
||||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.groups import GroupSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
|
@ -184,19 +184,31 @@ class UserMetricsSerializer(PassiveSerializer):
|
|||
def get_logins_per_1h(self, _):
|
||||
"""Get successful logins per hour for the last 24 hours"""
|
||||
user = self.context["user"]
|
||||
return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk)
|
||||
return (
|
||||
get_objects_for_user(user, "authentik_events.view_event")
|
||||
.filter(action=EventAction.LOGIN, user__pk=user.pk)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
||||
@extend_schema_field(CoordinateSerializer(many=True))
|
||||
def get_logins_failed_per_1h(self, _):
|
||||
"""Get failed logins per hour for the last 24 hours"""
|
||||
user = self.context["user"]
|
||||
return get_events_per_1h(action=EventAction.LOGIN_FAILED, context__username=user.username)
|
||||
return (
|
||||
get_objects_for_user(user, "authentik_events.view_event")
|
||||
.filter(action=EventAction.LOGIN_FAILED, context__username=user.username)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
||||
@extend_schema_field(CoordinateSerializer(many=True))
|
||||
def get_authorizations_per_1h(self, _):
|
||||
"""Get failed logins per hour for the last 24 hours"""
|
||||
user = self.context["user"]
|
||||
return get_events_per_1h(action=EventAction.AUTHORIZE_APPLICATION, user__pk=user.pk)
|
||||
return (
|
||||
get_objects_for_user(user, "authentik_events.view_event")
|
||||
.filter(action=EventAction.AUTHORIZE_APPLICATION, user__pk=user.pk)
|
||||
.get_events_per_hour()
|
||||
)
|
||||
|
||||
|
||||
class UsersFilter(FilterSet):
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Callable
|
|||
from uuid import uuid4
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from sentry_sdk.api import set_tag
|
||||
|
||||
SESSION_IMPERSONATE_USER = "authentik_impersonate_user"
|
||||
SESSION_IMPERSONATE_ORIGINAL_USER = "authentik_impersonate_original_user"
|
||||
|
@ -50,6 +51,7 @@ class RequestIDMiddleware:
|
|||
"request_id": request_id,
|
||||
"host": request.get_host(),
|
||||
}
|
||||
set_tag("authentik.request_id", request_id)
|
||||
response = self.get_response(request)
|
||||
response[RESPONSE_HEADER_ID] = request.request_id
|
||||
setattr(response, "ak_context", {})
|
||||
|
|
|
@ -359,13 +359,11 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
|||
"""Return component used to edit this object"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def ui_login_button(self) -> Optional[UILoginButton]:
|
||||
def ui_login_button(self, request: HttpRequest) -> Optional[UILoginButton]:
|
||||
"""If source uses a http-based flow, return UI Information about the login
|
||||
button. If source doesn't use http-based flow, return None."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or UserSettingSerializer."""
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<script src="{% static 'dist/poly.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from time import sleep
|
||||
from typing import Callable, Type
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
|
@ -30,6 +30,9 @@ class TestModels(TestCase):
|
|||
def source_tester_factory(test_model: Type[Stage]) -> Callable:
|
||||
"""Test source"""
|
||||
|
||||
factory = RequestFactory()
|
||||
request = factory.get("/")
|
||||
|
||||
def tester(self: TestModels):
|
||||
model_class = None
|
||||
if test_model._meta.abstract:
|
||||
|
@ -38,8 +41,8 @@ def source_tester_factory(test_model: Type[Stage]) -> Callable:
|
|||
model_class = test_model()
|
||||
model_class.slug = "test"
|
||||
self.assertIsNotNone(model_class.component)
|
||||
_ = model_class.ui_login_button
|
||||
_ = model_class.ui_user_settings
|
||||
_ = model_class.ui_login_button(request)
|
||||
_ = model_class.ui_user_settings()
|
||||
|
||||
return tester
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class TestPropertyMappingAPI(APITestCase):
|
|||
expr = "return True"
|
||||
self.assertEqual(PropertyMappingSerializer().validate_expression(expr), expr)
|
||||
with self.assertRaises(ValidationError):
|
||||
print(PropertyMappingSerializer().validate_expression("/"))
|
||||
PropertyMappingSerializer().validate_expression("/")
|
||||
|
||||
def test_types(self):
|
||||
"""Test PropertyMappigns's types endpoint"""
|
||||
|
|
|
@ -11,10 +11,13 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|||
from cryptography.x509 import Certificate, load_pem_x509_certificate
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.models import CreatedUpdatedModel
|
||||
from authentik.managed.models import ManagedModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
|
||||
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
|
||||
|
@ -62,7 +65,8 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
|
|||
password=None,
|
||||
backend=default_backend(),
|
||||
)
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
LOGGER.warning(exc)
|
||||
return None
|
||||
return self._private_key
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
from glob import glob
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
from cryptography.x509.base import load_pem_x509_certificate
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
@ -20,6 +23,22 @@ LOGGER = get_logger()
|
|||
MANAGED_DISCOVERED = "goauthentik.io/crypto/discovered/%s"
|
||||
|
||||
|
||||
def ensure_private_key_valid(body: str):
|
||||
"""Attempt loading of an RSA Private key without password"""
|
||||
load_pem_private_key(
|
||||
str.encode("\n".join([x.strip() for x in body.split("\n")])),
|
||||
password=None,
|
||||
backend=default_backend(),
|
||||
)
|
||||
return body
|
||||
|
||||
|
||||
def ensure_certificate_valid(body: str):
|
||||
"""Attempt loading of a PEM-encoded certificate"""
|
||||
load_pem_x509_certificate(body.encode("utf-8"), default_backend())
|
||||
return body
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task
|
||||
def certificate_discovery(self: MonitoredTask):
|
||||
|
@ -42,11 +61,11 @@ def certificate_discovery(self: MonitoredTask):
|
|||
with open(path, "r+", encoding="utf-8") as _file:
|
||||
body = _file.read()
|
||||
if "BEGIN RSA PRIVATE KEY" in body:
|
||||
private_keys[cert_name] = body
|
||||
private_keys[cert_name] = ensure_private_key_valid(body)
|
||||
else:
|
||||
certs[cert_name] = body
|
||||
except OSError as exc:
|
||||
LOGGER.warning("Failed to open file", exc=exc, file=path)
|
||||
certs[cert_name] = ensure_certificate_valid(body)
|
||||
except (OSError, ValueError) as exc:
|
||||
LOGGER.warning("Failed to open file or invalid format", exc=exc, file=path)
|
||||
discovered += 1
|
||||
for name, cert_data in certs.items():
|
||||
cert = CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED % name).first()
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Events API Views"""
|
||||
from json import loads
|
||||
|
||||
import django_filters
|
||||
from django.db.models.aggregates import Count
|
||||
from django.db.models.fields.json import KeyTextTransform
|
||||
|
@ -12,6 +14,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.admin.api.metrics import CoordinateSerializer
|
||||
from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
@ -110,13 +113,20 @@ class EventViewSet(ModelViewSet):
|
|||
@extend_schema(
|
||||
methods=["GET"],
|
||||
responses={200: EventTopPerUserSerializer(many=True)},
|
||||
filters=[],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
"action",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
),
|
||||
OpenApiParameter(
|
||||
"top_n",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
@action(detail=False, methods=["GET"], pagination_class=None)
|
||||
|
@ -137,6 +147,40 @@ class EventViewSet(ModelViewSet):
|
|||
.order_by("-counted_events")[:top_n]
|
||||
)
|
||||
|
||||
@extend_schema(
|
||||
methods=["GET"],
|
||||
responses={200: CoordinateSerializer(many=True)},
|
||||
filters=[],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
"action",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
),
|
||||
OpenApiParameter(
|
||||
"query",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@action(detail=False, methods=["GET"], pagination_class=None)
|
||||
def per_month(self, request: Request):
|
||||
"""Get the count of events per month"""
|
||||
filtered_action = request.query_params.get("action", EventAction.LOGIN)
|
||||
try:
|
||||
query = loads(request.query_params.get("query", "{}"))
|
||||
except ValueError:
|
||||
return Response(status=400)
|
||||
return Response(
|
||||
get_objects_for_user(request.user, "authentik_events.view_event")
|
||||
.filter(action=filtered_action)
|
||||
.filter(**query)
|
||||
.get_events_per_day()
|
||||
)
|
||||
|
||||
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
@action(detail=False, pagination_class=None, filter_backends=[])
|
||||
def actions(self, request: Request) -> Response:
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import Optional, TypedDict
|
|||
from geoip2.database import Reader
|
||||
from geoip2.errors import GeoIP2Error
|
||||
from geoip2.models import City
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
|
@ -62,13 +63,17 @@ class GeoIPReader:
|
|||
|
||||
def city(self, ip_address: str) -> Optional[City]:
|
||||
"""Wrapper for Reader.city"""
|
||||
if not self.enabled:
|
||||
return None
|
||||
self.__check_expired()
|
||||
try:
|
||||
return self.__reader.city(ip_address)
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
with Hub.current.start_span(
|
||||
op="authentik.events.geo.city",
|
||||
description=ip_address,
|
||||
):
|
||||
if not self.enabled:
|
||||
return None
|
||||
self.__check_expired()
|
||||
try:
|
||||
return self.__reader.city(ip_address)
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
|
||||
def city_dict(self, ip_address: str) -> Optional[GeoIPDict]:
|
||||
"""Wrapper for self.city that returns a dict"""
|
||||
|
|
|
@ -314,169 +314,10 @@ class Migration(migrations.Migration):
|
|||
old_name="user_json",
|
||||
new_name="user",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("sign_up", "Sign Up"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="event",
|
||||
name="date",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_created", "Invite Created"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotificationTransport",
|
||||
fields=[
|
||||
|
@ -610,68 +451,6 @@ class Migration(migrations.Migration):
|
|||
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("token_view", "Token View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=token_view_to_secret_view,
|
||||
),
|
||||
|
@ -688,76 +467,11 @@ class Migration(migrations.Migration):
|
|||
migrations.RunPython(
|
||||
code=update_expires,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="tenant",
|
||||
field=models.JSONField(blank=True, default=authentik.events.models.default_tenant),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("login", "Login"),
|
||||
("login_failed", "Login Failed"),
|
||||
("logout", "Logout"),
|
||||
("user_write", "User Write"),
|
||||
("suspicious_request", "Suspicious Request"),
|
||||
("password_set", "Password Set"),
|
||||
("secret_view", "Secret View"),
|
||||
("invitation_used", "Invite Used"),
|
||||
("authorize_application", "Authorize Application"),
|
||||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
("system_task_execution", "System Task Execution"),
|
||||
("system_task_exception", "System Task Exception"),
|
||||
("system_exception", "System Exception"),
|
||||
("configuration_error", "Configuration Error"),
|
||||
("model_created", "Model Created"),
|
||||
("model_updated", "Model Updated"),
|
||||
("model_deleted", "Model Deleted"),
|
||||
("email_sent", "Email Sent"),
|
||||
("update_available", "Update Available"),
|
||||
("custom_", "Custom Prefix"),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="event",
|
||||
name="action",
|
||||
|
@ -776,6 +490,7 @@ class Migration(migrations.Migration):
|
|||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("flow_execution", "Flow Execution"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""authentik events models"""
|
||||
import time
|
||||
from collections import Counter
|
||||
from datetime import timedelta
|
||||
from inspect import getmodule, stack
|
||||
from smtplib import SMTPException
|
||||
|
@ -7,6 +9,12 @@ from uuid import uuid4
|
|||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models import Count, ExpressionWrapper, F
|
||||
from django.db.models.fields import DurationField
|
||||
from django.db.models.functions import ExtractHour
|
||||
from django.db.models.functions.datetime import ExtractDay
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.timezone import now
|
||||
|
@ -70,6 +78,7 @@ class EventAction(models.TextChoices):
|
|||
IMPERSONATION_STARTED = "impersonation_started"
|
||||
IMPERSONATION_ENDED = "impersonation_ended"
|
||||
|
||||
FLOW_EXECUTION = "flow_execution"
|
||||
POLICY_EXECUTION = "policy_execution"
|
||||
POLICY_EXCEPTION = "policy_exception"
|
||||
PROPERTY_MAPPING_EXCEPTION = "property_mapping_exception"
|
||||
|
@ -90,6 +99,72 @@ class EventAction(models.TextChoices):
|
|||
CUSTOM_PREFIX = "custom_"
|
||||
|
||||
|
||||
class EventQuerySet(QuerySet):
|
||||
"""Custom events query set with helper functions"""
|
||||
|
||||
def get_events_per_hour(self) -> list[dict[str, int]]:
|
||||
"""Get event count by hour in the last day, fill with zeros"""
|
||||
date_from = now() - timedelta(days=1)
|
||||
result = (
|
||||
self.filter(created__gte=date_from)
|
||||
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
|
||||
.annotate(age_hours=ExtractHour("age"))
|
||||
.values("age_hours")
|
||||
.annotate(count=Count("pk"))
|
||||
.order_by("age_hours")
|
||||
)
|
||||
data = Counter({int(d["age_hours"]): d["count"] for d in result})
|
||||
results = []
|
||||
_now = now()
|
||||
for hour in range(0, -24, -1):
|
||||
results.append(
|
||||
{
|
||||
"x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000,
|
||||
"y_cord": data[hour * -1],
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
def get_events_per_day(self) -> list[dict[str, int]]:
|
||||
"""Get event count by hour in the last day, fill with zeros"""
|
||||
date_from = now() - timedelta(weeks=4)
|
||||
result = (
|
||||
self.filter(created__gte=date_from)
|
||||
.annotate(age=ExpressionWrapper(now() - F("created"), output_field=DurationField()))
|
||||
.annotate(age_days=ExtractDay("age"))
|
||||
.values("age_days")
|
||||
.annotate(count=Count("pk"))
|
||||
.order_by("age_days")
|
||||
)
|
||||
data = Counter({int(d["age_days"]): d["count"] for d in result})
|
||||
results = []
|
||||
_now = now()
|
||||
for day in range(0, -30, -1):
|
||||
results.append(
|
||||
{
|
||||
"x_cord": time.mktime((_now + timedelta(days=day)).timetuple()) * 1000,
|
||||
"y_cord": data[day * -1],
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
class EventManager(Manager):
|
||||
"""Custom helper methods for Events"""
|
||||
|
||||
def get_queryset(self) -> QuerySet:
|
||||
"""use custom queryset"""
|
||||
return EventQuerySet(self.model, using=self._db)
|
||||
|
||||
def get_events_per_hour(self) -> list[dict[str, int]]:
|
||||
"""Wrap method from queryset"""
|
||||
return self.get_queryset().get_events_per_hour()
|
||||
|
||||
def get_events_per_day(self) -> list[dict[str, int]]:
|
||||
"""Wrap method from queryset"""
|
||||
return self.get_queryset().get_events_per_day()
|
||||
|
||||
|
||||
class Event(ExpiringModel):
|
||||
"""An individual Audit/Metrics/Notification/Error Event"""
|
||||
|
||||
|
@ -105,6 +180,8 @@ class Event(ExpiringModel):
|
|||
# Shadow the expires attribute from ExpiringModel to override the default duration
|
||||
expires = models.DateTimeField(default=default_event_duration)
|
||||
|
||||
objects = EventManager()
|
||||
|
||||
@staticmethod
|
||||
def _get_app_from_request(request: HttpRequest) -> str:
|
||||
if not isinstance(request, HttpRequest):
|
||||
|
|
|
@ -46,7 +46,7 @@ class TaskResult:
|
|||
|
||||
def with_error(self, exc: Exception) -> "TaskResult":
|
||||
"""Since errors might not always be pickle-able, set the traceback"""
|
||||
self.messages.extend(exception_to_string(exc).splitlines())
|
||||
self.messages.append(str(exc))
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class StageViewSet(
|
|||
stages += list(configurable_stage.objects.all().order_by("name"))
|
||||
matching_stages: list[dict] = []
|
||||
for stage in stages:
|
||||
user_settings = stage.ui_user_settings
|
||||
user_settings = stage.ui_user_settings()
|
||||
if not user_settings:
|
||||
continue
|
||||
user_settings.initial_data["object_uid"] = str(stage.pk)
|
||||
|
|
|
@ -75,7 +75,6 @@ class Stage(SerializerModel):
|
|||
"""Return component used to edit this object"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or a challenge."""
|
||||
|
|
|
@ -126,7 +126,9 @@ class FlowPlanner:
|
|||
) -> FlowPlan:
|
||||
"""Check each of the flows' policies, check policies for each stage with PolicyBinding
|
||||
and return ordered list"""
|
||||
with Hub.current.start_span(op="flow.planner.plan", description=self.flow.slug) as span:
|
||||
with Hub.current.start_span(
|
||||
op="authentik.flow.planner.plan", description=self.flow.slug
|
||||
) as span:
|
||||
span: Span
|
||||
span.set_data("flow", self.flow)
|
||||
span.set_data("request", request)
|
||||
|
@ -181,7 +183,7 @@ class FlowPlanner:
|
|||
"""Build flow plan by checking each stage in their respective
|
||||
order and checking the applied policies"""
|
||||
with Hub.current.start_span(
|
||||
op="flow.planner.build_plan",
|
||||
op="authentik.flow.planner.build_plan",
|
||||
description=self.flow.slug,
|
||||
) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time():
|
||||
span: Span
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.http.response import HttpResponse
|
|||
from django.urls import reverse
|
||||
from django.views.generic.base import View
|
||||
from rest_framework.request import Request
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import DEFAULT_AVATAR, User
|
||||
|
@ -94,8 +95,16 @@ class ChallengeStageView(StageView):
|
|||
keep_context=keep_context,
|
||||
)
|
||||
return self.executor.restart_flow(keep_context)
|
||||
return self.challenge_invalid(challenge)
|
||||
return self.challenge_valid(challenge)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.flow.stage.challenge_invalid",
|
||||
description=self.__class__.__name__,
|
||||
):
|
||||
return self.challenge_invalid(challenge)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.flow.stage.challenge_valid",
|
||||
description=self.__class__.__name__,
|
||||
):
|
||||
return self.challenge_valid(challenge)
|
||||
|
||||
def format_title(self) -> str:
|
||||
"""Allow usage of placeholder in flow title."""
|
||||
|
@ -104,7 +113,11 @@ class ChallengeStageView(StageView):
|
|||
}
|
||||
|
||||
def _get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
challenge = self.get_challenge(*args, **kwargs)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.flow.stage.get_challenge",
|
||||
description=self.__class__.__name__,
|
||||
):
|
||||
challenge = self.get_challenge(*args, **kwargs)
|
||||
if "flow_info" not in challenge.initial_data:
|
||||
flow_info = ContextualFlowInfo(
|
||||
data={
|
||||
|
|
|
@ -32,7 +32,7 @@ class TestFlowsAPI(APITestCase):
|
|||
|
||||
def test_models(self):
|
||||
"""Test that ui_user_settings returns none"""
|
||||
self.assertIsNone(Stage().ui_user_settings)
|
||||
self.assertIsNone(Stage().ui_user_settings())
|
||||
|
||||
def test_api_serializer(self):
|
||||
"""Test that stage serializer returns the correct type"""
|
||||
|
|
|
@ -23,7 +23,7 @@ def model_tester_factory(test_model: Type[Stage]) -> Callable:
|
|||
model_class = test_model()
|
||||
self.assertTrue(issubclass(model_class.type, StageView))
|
||||
self.assertIsNotNone(test_model.component)
|
||||
_ = model_class.ui_user_settings
|
||||
_ = model_class.ui_user_settings()
|
||||
|
||||
return tester
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ class FlowExecutorView(APIView):
|
|||
# pylint: disable=unused-argument, too-many-return-statements
|
||||
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
|
||||
with Hub.current.start_span(
|
||||
op="flow.executor.dispatch", description=self.flow.slug
|
||||
op="authentik.flow.executor.dispatch", description=self.flow.slug
|
||||
) as span:
|
||||
span.set_data("authentik Flow", self.flow.slug)
|
||||
get_params = QueryDict(request.GET.get("query", ""))
|
||||
|
@ -275,7 +275,7 @@ class FlowExecutorView(APIView):
|
|||
)
|
||||
try:
|
||||
with Hub.current.start_span(
|
||||
op="flow.executor.stage",
|
||||
op="authentik.flow.executor.stage",
|
||||
description=class_to_path(self.current_stage_view.__class__),
|
||||
) as span:
|
||||
span.set_data("Method", "GET")
|
||||
|
@ -319,7 +319,7 @@ class FlowExecutorView(APIView):
|
|||
)
|
||||
try:
|
||||
with Hub.current.start_span(
|
||||
op="flow.executor.stage",
|
||||
op="authentik.flow.executor.stage",
|
||||
description=class_to_path(self.current_stage_view.__class__),
|
||||
) as span:
|
||||
span.set_data("Method", "POST")
|
||||
|
@ -371,6 +371,12 @@ class FlowExecutorView(APIView):
|
|||
NEXT_ARG_NAME, "authentik_core:root-redirect"
|
||||
)
|
||||
self.cancel()
|
||||
Event.new(
|
||||
action=EventAction.FLOW_EXECUTION,
|
||||
flow=self.flow,
|
||||
designation=self.flow.designation,
|
||||
successful=True,
|
||||
).from_http(self.request)
|
||||
return to_stage_response(self.request, redirect_with_qs(next_param))
|
||||
|
||||
def stage_ok(self) -> HttpResponse:
|
||||
|
|
|
@ -106,7 +106,7 @@ class FlowInspectorView(APIView):
|
|||
else:
|
||||
try:
|
||||
current_plan = request.session[SESSION_KEY_HISTORY][-1]
|
||||
except KeyError:
|
||||
except IndexError:
|
||||
return Response(status=400)
|
||||
is_completed = True
|
||||
current_serializer = FlowInspectorPlanSerializer(
|
||||
|
|
|
@ -80,8 +80,9 @@ class BaseEvaluator:
|
|||
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
|
||||
If any exception is raised during execution, it is raised.
|
||||
The result is returned without any type-checking."""
|
||||
with Hub.current.start_span(op="lib.evaluator.evaluate") as span:
|
||||
with Hub.current.start_span(op="authentik.lib.evaluator.evaluate") as span:
|
||||
span: Span
|
||||
span.description = self._filename
|
||||
span.set_data("expression", expression_source)
|
||||
param_keys = self._context.keys()
|
||||
try:
|
||||
|
|
|
@ -90,7 +90,7 @@ class PolicyEngine:
|
|||
def build(self) -> "PolicyEngine":
|
||||
"""Build wrapper which monitors performance"""
|
||||
with Hub.current.start_span(
|
||||
op="policy.engine.build",
|
||||
op="authentik.policy.engine.build",
|
||||
description=self.__pbm,
|
||||
) as span, HIST_POLICIES_BUILD_TIME.labels(
|
||||
object_name=self.__pbm,
|
||||
|
|
|
@ -66,6 +66,7 @@ class Migration(migrations.Migration):
|
|||
("source_linked", "Source Linked"),
|
||||
("impersonation_started", "Impersonation Started"),
|
||||
("impersonation_ended", "Impersonation Ended"),
|
||||
("flow_execution", "Flow Execution"),
|
||||
("policy_execution", "Policy Execution"),
|
||||
("policy_exception", "Policy Exception"),
|
||||
("property_mapping_exception", "Property Mapping Exception"),
|
||||
|
|
|
@ -74,4 +74,4 @@ class TestExpressionPolicyAPI(APITestCase):
|
|||
expr = "return True"
|
||||
self.assertEqual(ExpressionPolicySerializer().validate_expression(expr), expr)
|
||||
with self.assertRaises(ValidationError):
|
||||
print(ExpressionPolicySerializer().validate_expression("/"))
|
||||
ExpressionPolicySerializer().validate_expression("/")
|
||||
|
|
|
@ -130,7 +130,7 @@ class PolicyProcess(PROCESS_CLASS):
|
|||
def profiling_wrapper(self):
|
||||
"""Run with profiling enabled"""
|
||||
with Hub.current.start_span(
|
||||
op="policy.process.execute",
|
||||
op="authentik.policy.process.execute",
|
||||
) as span, HIST_POLICIES_EXECUTION_TIME.labels(
|
||||
binding_order=self.binding.order,
|
||||
binding_target_type=self.binding.target_type,
|
||||
|
|
|
@ -8,7 +8,6 @@ from datetime import datetime
|
|||
from hashlib import sha256
|
||||
from typing import Any, Optional, Type
|
||||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from dacite import from_dict
|
||||
from django.db import models
|
||||
|
@ -225,7 +224,7 @@ class OAuth2Provider(Provider):
|
|||
token = RefreshToken(
|
||||
user=user,
|
||||
provider=self,
|
||||
refresh_token=uuid4().hex,
|
||||
refresh_token=generate_key(),
|
||||
expires=timezone.now() + timedelta_from_string(self.token_validity),
|
||||
scope=scope,
|
||||
)
|
||||
|
@ -434,7 +433,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
|
|||
"""Create access token with a similar format as Okta, Keycloak, ADFS"""
|
||||
token = self.create_id_token(user, request).to_dict()
|
||||
token["cid"] = self.provider.client_id
|
||||
token["uid"] = uuid4().hex
|
||||
token["uid"] = generate_key()
|
||||
return self.provider.encode(token)
|
||||
|
||||
def create_id_token(self, user: User, request: HttpRequest) -> IDToken:
|
||||
|
|
|
@ -194,8 +194,10 @@ class TokenView(View):
|
|||
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
|
||||
|
||||
if self.params.grant_type == GRANT_TYPE_AUTHORIZATION_CODE:
|
||||
LOGGER.info("Converting authorization code to refresh token")
|
||||
return TokenResponse(self.create_code_response())
|
||||
if self.params.grant_type == GRANT_TYPE_REFRESH_TOKEN:
|
||||
LOGGER.info("Refreshing refresh token")
|
||||
return TokenResponse(self.create_refresh_response())
|
||||
raise ValueError(f"Invalid grant_type: {self.params.grant_type}")
|
||||
except TokenError as error:
|
||||
|
|
|
@ -70,13 +70,14 @@ class AssertionProcessor:
|
|||
"""Get AttributeStatement Element with Attributes from Property Mappings."""
|
||||
# https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
|
||||
attribute_statement = Element(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")
|
||||
user = self.http_request.user
|
||||
for mapping in self.provider.property_mappings.all().select_subclasses():
|
||||
if not isinstance(mapping, SAMLPropertyMapping):
|
||||
continue
|
||||
try:
|
||||
mapping: SAMLPropertyMapping
|
||||
value = mapping.evaluate(
|
||||
user=self.http_request.user,
|
||||
user=user,
|
||||
request=self.http_request,
|
||||
provider=self.provider,
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""Source API Views"""
|
||||
from typing import Any
|
||||
|
||||
from django.utils.text import slugify
|
||||
from django_filters.filters import AllValuesMultipleFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
@ -110,7 +109,8 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
task = TaskInfo.by_name(f"ldap_sync_{slugify(source.name)}-{sync_class.__name__}")
|
||||
sync_name = sync_class.__name__.replace("LDAPSynchronizer", "").lower()
|
||||
task = TaskInfo.by_name(f"ldap_sync_{source.slug}_{sync_name}")
|
||||
if task:
|
||||
results.append(task)
|
||||
return Response(TaskSerializer(results, many=True).data)
|
||||
|
|
|
@ -29,7 +29,7 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
group_dn = self._flatten(self._flatten(group.get("entryDN", group.get("dn"))))
|
||||
if self._source.object_uniqueness_field not in attributes:
|
||||
self.message(
|
||||
f"Cannot find uniqueness field in attributes: '{group_dn}",
|
||||
f"Cannot find uniqueness field in attributes: '{group_dn}'",
|
||||
attributes=attributes.keys(),
|
||||
dn=group_dn,
|
||||
)
|
||||
|
|
|
@ -31,7 +31,7 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
user_dn = self._flatten(user.get("entryDN", user.get("dn")))
|
||||
if self._source.object_uniqueness_field not in attributes:
|
||||
self.message(
|
||||
f"Cannot find uniqueness field in attributes: '{user_dn}",
|
||||
f"Cannot find uniqueness field in attributes: '{user_dn}'",
|
||||
attributes=attributes.keys(),
|
||||
dn=user_dn,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""LDAP Sync tasks"""
|
||||
from django.utils.text import slugify
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
@ -39,7 +38,7 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
|
|||
# to set the state with
|
||||
return
|
||||
sync = path_to_class(sync_class)
|
||||
self.set_uid(f"{slugify(source.name)}_{sync.__name__.replace('LDAPSynchronizer', '').lower()}")
|
||||
self.set_uid(f"{source.slug}_{sync.__name__.replace('LDAPSynchronizer', '').lower()}")
|
||||
try:
|
||||
sync_inst = sync(source)
|
||||
count = sync_inst.sync()
|
||||
|
|
|
@ -14,6 +14,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
|||
"authentik.sources.oauth.types.github",
|
||||
"authentik.sources.oauth.types.google",
|
||||
"authentik.sources.oauth.types.oidc",
|
||||
"authentik.sources.oauth.types.okta",
|
||||
"authentik.sources.oauth.types.reddit",
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
]
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
from typing import TYPE_CHECKING, Optional, Type
|
||||
|
||||
from django.db import models
|
||||
from django.http.request import HttpRequest
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import Source, UserSourceConnection
|
||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||
from authentik.flows.challenge import ChallengeTypes, RedirectChallenge
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.sources.oauth.types.manager import SourceType
|
||||
|
@ -64,24 +64,15 @@ class OAuthSource(Source):
|
|||
|
||||
return OAuthSourceSerializer
|
||||
|
||||
@property
|
||||
def ui_login_button(self) -> UILoginButton:
|
||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
||||
provider_type = self.type
|
||||
provider = provider_type()
|
||||
return UILoginButton(
|
||||
challenge=RedirectChallenge(
|
||||
instance={
|
||||
"type": ChallengeTypes.REDIRECT.value,
|
||||
"to": reverse(
|
||||
"authentik_sources_oauth:oauth-client-login",
|
||||
kwargs={"source_slug": self.slug},
|
||||
),
|
||||
}
|
||||
),
|
||||
icon_url=provider_type().icon_url(),
|
||||
name=self.name,
|
||||
icon_url=provider.icon_url(),
|
||||
challenge=provider.login_challenge(self, request),
|
||||
)
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
@ -183,6 +174,16 @@ class AppleOAuthSource(OAuthSource):
|
|||
verbose_name_plural = _("Apple OAuth Sources")
|
||||
|
||||
|
||||
class OktaOAuthSource(OAuthSource):
|
||||
"""Login using a okta.com."""
|
||||
|
||||
class Meta:
|
||||
|
||||
abstract = True
|
||||
verbose_name = _("Okta OAuth Source")
|
||||
verbose_name_plural = _("Okta OAuth Sources")
|
||||
|
||||
|
||||
class UserOAuthSourceConnection(UserSourceConnection):
|
||||
"""Authorized remote OAuth provider."""
|
||||
|
||||
|
|
|
@ -2,10 +2,15 @@
|
|||
from time import time
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.http.request import HttpRequest
|
||||
from django.urls.base import reverse
|
||||
from jwt import decode, encode
|
||||
from rest_framework.fields import CharField
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
||||
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.manager import MANAGER, SourceType
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
@ -13,18 +18,34 @@ from authentik.sources.oauth.views.redirect import OAuthRedirect
|
|||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class AppleLoginChallenge(Challenge):
|
||||
"""Special challenge for apple-native authentication flow, which happens on the client."""
|
||||
|
||||
client_id = CharField()
|
||||
component = CharField(default="ak-flow-sources-oauth-apple")
|
||||
scope = CharField()
|
||||
redirect_uri = CharField()
|
||||
state = CharField()
|
||||
|
||||
|
||||
class AppleChallengeResponse(ChallengeResponse):
|
||||
"""Pseudo class for plex response"""
|
||||
|
||||
component = CharField(default="ak-flow-sources-oauth-apple")
|
||||
|
||||
|
||||
class AppleOAuthClient(OAuth2Client):
|
||||
"""Apple OAuth2 client"""
|
||||
|
||||
def get_client_id(self) -> str:
|
||||
parts = self.source.consumer_key.split(";")
|
||||
parts: list[str] = self.source.consumer_key.split(";")
|
||||
if len(parts) < 3:
|
||||
return self.source.consumer_key
|
||||
return parts[0]
|
||||
return parts[0].strip()
|
||||
|
||||
def get_client_secret(self) -> str:
|
||||
now = time()
|
||||
parts = self.source.consumer_key.split(";")
|
||||
parts: list[str] = self.source.consumer_key.split(";")
|
||||
if len(parts) < 3:
|
||||
raise ValueError(
|
||||
(
|
||||
|
@ -34,14 +55,14 @@ class AppleOAuthClient(OAuth2Client):
|
|||
)
|
||||
LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2])
|
||||
payload = {
|
||||
"iss": parts[1],
|
||||
"iss": parts[1].strip(),
|
||||
"iat": now,
|
||||
"exp": now + 86400 * 180,
|
||||
"aud": "https://appleid.apple.com",
|
||||
"sub": parts[0],
|
||||
"sub": parts[0].strip(),
|
||||
}
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2]})
|
||||
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2].strip()})
|
||||
LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt)
|
||||
return jwt
|
||||
|
||||
|
@ -55,7 +76,7 @@ class AppleOAuthRedirect(OAuthRedirect):
|
|||
|
||||
client_class = AppleOAuthClient
|
||||
|
||||
def get_additional_parameters(self, source): # pragma: no cover
|
||||
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
|
||||
return {
|
||||
"scope": "name email",
|
||||
"response_mode": "form_post",
|
||||
|
@ -74,7 +95,6 @@ class AppleOAuth2Callback(OAuthCallback):
|
|||
self,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
print(info)
|
||||
return {
|
||||
"email": info.get("email"),
|
||||
"name": info.get("name"),
|
||||
|
@ -96,3 +116,24 @@ class AppleType(SourceType):
|
|||
|
||||
def icon_url(self) -> str:
|
||||
return "https://appleid.cdn-apple.com/appleid/button/logo"
|
||||
|
||||
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
|
||||
"""Pre-general all the things required for the JS SDK"""
|
||||
apple_client = AppleOAuthClient(
|
||||
source,
|
||||
request,
|
||||
callback=reverse(
|
||||
"authentik_sources_oauth:oauth-client-callback",
|
||||
kwargs={"source_slug": source.slug},
|
||||
),
|
||||
)
|
||||
args = apple_client.get_redirect_args()
|
||||
return AppleLoginChallenge(
|
||||
instance={
|
||||
"client_id": apple_client.get_client_id(),
|
||||
"scope": "name email",
|
||||
"redirect_uri": args["redirect_uri"],
|
||||
"state": args["state"],
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -2,9 +2,13 @@
|
|||
from enum import Enum
|
||||
from typing import Callable, Optional, Type
|
||||
|
||||
from django.http.request import HttpRequest
|
||||
from django.templatetags.static import static
|
||||
from django.urls.base import reverse
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.flows.challenge import Challenge, ChallengeTypes, RedirectChallenge
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
@ -37,6 +41,19 @@ class SourceType:
|
|||
"""Get Icon URL for login"""
|
||||
return static(f"authentik/sources/{self.slug}.svg")
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
|
||||
"""Allow types to return custom challenges"""
|
||||
return RedirectChallenge(
|
||||
instance={
|
||||
"type": ChallengeTypes.REDIRECT.value,
|
||||
"to": reverse(
|
||||
"authentik_sources_oauth:oauth-client-login",
|
||||
kwargs={"source_slug": source.slug},
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class SourceTypeManager:
|
||||
"""Manager to hold all Source types."""
|
||||
|
|
51
authentik/sources/oauth/types/okta.py
Normal file
51
authentik/sources/oauth/types/okta.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
"""Okta OAuth Views"""
|
||||
from typing import Any
|
||||
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.azure_ad import AzureADClient
|
||||
from authentik.sources.oauth.types.manager import MANAGER, SourceType
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
class OktaOAuthRedirect(OAuthRedirect):
|
||||
"""Okta OAuth2 Redirect"""
|
||||
|
||||
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
|
||||
return {
|
||||
"scope": "openid email profile",
|
||||
}
|
||||
|
||||
|
||||
class OktaOAuth2Callback(OAuthCallback):
|
||||
"""Okta OAuth2 Callback"""
|
||||
|
||||
# Okta has the same quirk as azure and throws an error if the access token
|
||||
# is set via query parameter, so we re-use the azure client
|
||||
# see https://github.com/goauthentik/authentik/issues/1910
|
||||
client_class = AzureADClient
|
||||
|
||||
def get_user_id(self, info: dict[str, str]) -> str:
|
||||
return info.get("sub", "")
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"username": info.get("nickname"),
|
||||
"email": info.get("email"),
|
||||
"name": info.get("name"),
|
||||
}
|
||||
|
||||
|
||||
@MANAGER.type()
|
||||
class OktaType(SourceType):
|
||||
"""Okta Type definition"""
|
||||
|
||||
callback_view = OktaOAuth2Callback
|
||||
redirect_view = OktaOAuthRedirect
|
||||
name = "Okta"
|
||||
slug = "okta"
|
||||
|
||||
urls_customizable = True
|
|
@ -3,6 +3,7 @@ from typing import Optional
|
|||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.http.request import HttpRequest
|
||||
from django.templatetags.static import static
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.fields import CharField
|
||||
|
@ -62,8 +63,7 @@ class PlexSource(Source):
|
|||
|
||||
return PlexSourceSerializer
|
||||
|
||||
@property
|
||||
def ui_login_button(self) -> UILoginButton:
|
||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
||||
return UILoginButton(
|
||||
challenge=PlexAuthenticationChallenge(
|
||||
{
|
||||
|
@ -77,7 +77,6 @@ class PlexSource(Source):
|
|||
name=self.name,
|
||||
)
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -167,8 +167,7 @@ class SAMLSource(Source):
|
|||
reverse(f"authentik_sources_saml:{view}", kwargs={"source_slug": self.slug})
|
||||
)
|
||||
|
||||
@property
|
||||
def ui_login_button(self) -> UILoginButton:
|
||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
||||
return UILoginButton(
|
||||
challenge=RedirectChallenge(
|
||||
instance={
|
||||
|
|
|
@ -48,7 +48,6 @@ class AuthenticatorDuoStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-duo-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -141,7 +141,6 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-sms-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -90,6 +90,5 @@ class AuthenticatorSMSStageTests(APITestCase):
|
|||
"code": int(self.client.session[SESSION_SMS_DEVICE].token),
|
||||
},
|
||||
)
|
||||
print(response.content)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
sms_send_mock.assert_not_called()
|
||||
|
|
|
@ -31,7 +31,6 @@ class AuthenticatorStaticStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-static-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -38,7 +38,6 @@ class AuthenticatorTOTPStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-totp-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -18,7 +18,7 @@ class AuthenticateWebAuthnStageSerializer(StageSerializer):
|
|||
class Meta:
|
||||
|
||||
model = AuthenticateWebAuthnStage
|
||||
fields = StageSerializer.Meta.fields + ["configure_flow"]
|
||||
fields = StageSerializer.Meta.fields + ["configure_flow", "user_verification"]
|
||||
|
||||
|
||||
class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet):
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 4.0 on 2021-12-14 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_webauthn", "0004_auto_20210304_1850"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="authenticatewebauthnstage",
|
||||
name="user_verification",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("required", "Required"),
|
||||
("preferred", "Preferred"),
|
||||
("discouraged", "Discouraged"),
|
||||
],
|
||||
default="preferred",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -15,9 +15,30 @@ from authentik.core.types import UserSettingSerializer
|
|||
from authentik.flows.models import ConfigurableStage, Stage
|
||||
|
||||
|
||||
class UserVerification(models.TextChoices):
|
||||
"""The degree to which the Relying Party wishes to verify a user's identity.
|
||||
|
||||
Members:
|
||||
`REQUIRED`: User verification must occur
|
||||
`PREFERRED`: User verification would be great, but if not that's okay too
|
||||
`DISCOURAGED`: User verification should not occur, but it's okay if it does
|
||||
|
||||
https://www.w3.org/TR/webauthn-2/#enumdef-userverificationrequirement
|
||||
"""
|
||||
|
||||
REQUIRED = "required"
|
||||
PREFERRED = "preferred"
|
||||
DISCOURAGED = "discouraged"
|
||||
|
||||
|
||||
class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
|
||||
"""WebAuthn stage"""
|
||||
|
||||
user_verification = models.TextField(
|
||||
choices=UserVerification.choices,
|
||||
default=UserVerification.PREFERRED,
|
||||
)
|
||||
|
||||
@property
|
||||
def serializer(self) -> BaseSerializer:
|
||||
from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageSerializer
|
||||
|
@ -34,7 +55,6 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-authenticator-webauthn-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
|
|
|
@ -14,7 +14,6 @@ from webauthn.helpers.structs import (
|
|||
PublicKeyCredentialCreationOptions,
|
||||
RegistrationCredential,
|
||||
ResidentKeyRequirement,
|
||||
UserVerificationRequirement,
|
||||
)
|
||||
from webauthn.registration.verify_registration_response import VerifiedRegistration
|
||||
|
||||
|
@ -27,7 +26,7 @@ from authentik.flows.challenge import (
|
|||
)
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage, WebAuthnDevice
|
||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
@ -83,7 +82,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
# clear session variables prior to starting a new registration
|
||||
self.request.session.pop("challenge", None)
|
||||
|
||||
stage: AuthenticateWebAuthnStage = self.executor.current_stage
|
||||
user = self.get_pending_user()
|
||||
|
||||
registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
|
||||
|
@ -94,10 +93,9 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||
user_display_name=user.name,
|
||||
authenticator_selection=AuthenticatorSelectionCriteria(
|
||||
resident_key=ResidentKeyRequirement.PREFERRED,
|
||||
user_verification=UserVerificationRequirement.PREFERRED,
|
||||
user_verification=str(stage.user_verification),
|
||||
),
|
||||
)
|
||||
registration_options.user.id = user.uid
|
||||
|
||||
self.request.session["challenge"] = registration_options.challenge
|
||||
return AuthenticatorWebAuthnChallenge(
|
||||
|
|
|
@ -29,4 +29,4 @@ class TestEmailStageAPI(APITestCase):
|
|||
EmailTemplates.ACCOUNT_CONFIRM,
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
print(EmailStageSerializer().validate_template("foobar"))
|
||||
EmailStageSerializer().validate_template("foobar")
|
||||
|
|
|
@ -12,6 +12,7 @@ from django.utils.translation import gettext as _
|
|||
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
|
||||
from rest_framework.fields import BooleanField, CharField, DictField, ListField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
|
@ -25,6 +26,7 @@ from authentik.flows.challenge import (
|
|||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE
|
||||
from authentik.sources.oauth.types.apple import AppleLoginChallenge
|
||||
from authentik.sources.plex.models import PlexAuthenticationChallenge
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from authentik.stages.identification.signals import identification_failed
|
||||
|
@ -39,6 +41,7 @@ LOGGER = get_logger()
|
|||
serializers={
|
||||
RedirectChallenge().fields["component"].default: RedirectChallenge,
|
||||
PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge,
|
||||
AppleLoginChallenge().fields["component"].default: AppleLoginChallenge,
|
||||
},
|
||||
resource_type_field_name="component",
|
||||
)
|
||||
|
@ -88,8 +91,12 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||
|
||||
pre_user = self.stage.get_user(uid_field)
|
||||
if not pre_user:
|
||||
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
|
||||
sleep(0.30 * SystemRandom().randint(3, 7))
|
||||
with Hub.current.start_span(
|
||||
op="authentik.stages.identification.validate_invalid_wait",
|
||||
description="Sleep random time on invalid user identifier",
|
||||
):
|
||||
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
|
||||
sleep(0.030 * SystemRandom().randint(3, 7))
|
||||
LOGGER.debug("invalid_login", identifier=uid_field)
|
||||
identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)
|
||||
# We set the pending_user even on failure so it's part of the context, even
|
||||
|
@ -112,12 +119,16 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
|||
if not password:
|
||||
LOGGER.warning("Password not set for ident+auth attempt")
|
||||
try:
|
||||
user = authenticate(
|
||||
self.stage.request,
|
||||
current_stage.password_stage.backends,
|
||||
username=self.pre_user.username,
|
||||
password=password,
|
||||
)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.stages.identification.authenticate",
|
||||
description="User authenticate call (combo stage)",
|
||||
):
|
||||
user = authenticate(
|
||||
self.stage.request,
|
||||
current_stage.password_stage.backends,
|
||||
username=self.pre_user.username,
|
||||
password=password,
|
||||
)
|
||||
if not user:
|
||||
raise ValidationError("Failed to authenticate.")
|
||||
self.pre_user = user
|
||||
|
@ -191,7 +202,7 @@ class IdentificationStageView(ChallengeStageView):
|
|||
current_stage.sources.filter(enabled=True).order_by("name").select_subclasses()
|
||||
)
|
||||
for source in sources:
|
||||
ui_login_button = source.ui_login_button
|
||||
ui_login_button = source.ui_login_button(self.request)
|
||||
if ui_login_button:
|
||||
button = asdict(ui_login_button)
|
||||
button["challenge"] = ui_login_button.challenge.data
|
||||
|
|
|
@ -5,8 +5,8 @@ from rest_framework.fields import JSONField
|
|||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.groups import GroupMemberSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.api.utils import is_dict
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||
|
@ -46,7 +46,7 @@ class InvitationStageViewSet(UsedByMixin, ModelViewSet):
|
|||
class InvitationSerializer(ModelSerializer):
|
||||
"""Invitation Serializer"""
|
||||
|
||||
created_by = UserSerializer(read_only=True)
|
||||
created_by = GroupMemberSerializer(read_only=True)
|
||||
fixed_data = JSONField(validators=[is_dict], required=False)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -63,7 +63,6 @@ class PasswordStage(ConfigurableStage, Stage):
|
|||
def component(self) -> str:
|
||||
return "ak-stage-password-form"
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
if not self.configure_flow:
|
||||
return None
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.urls import reverse
|
|||
from django.utils.translation import gettext as _
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
|
@ -43,7 +44,11 @@ def authenticate(request: HttpRequest, backends: list[str], **credentials: Any)
|
|||
LOGGER.warning("Failed to import backend", path=backend_path)
|
||||
continue
|
||||
LOGGER.debug("Attempting authentication...", backend=backend_path)
|
||||
user = backend.authenticate(request, **credentials)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.stages.password.authenticate",
|
||||
description=backend_path,
|
||||
):
|
||||
user = backend.authenticate(request, **credentials)
|
||||
if user is None:
|
||||
LOGGER.debug("Backend returned nothing, continuing", backend=backend_path)
|
||||
continue
|
||||
|
@ -120,7 +125,13 @@ class PasswordStageView(ChallengeStageView):
|
|||
"username": pending_user.username,
|
||||
}
|
||||
try:
|
||||
user = authenticate(self.request, self.executor.current_stage.backends, **auth_kwargs)
|
||||
with Hub.current.start_span(
|
||||
op="authentik.stages.password.authenticate",
|
||||
description="User authenticate call",
|
||||
):
|
||||
user = authenticate(
|
||||
self.request, self.executor.current_stage.backends, **auth_kwargs
|
||||
)
|
||||
except PermissionDenied:
|
||||
del auth_kwargs["password"]
|
||||
# User was found, but permission was denied (i.e. user is not active)
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Any
|
|||
from django.db.models import F, Q
|
||||
from django.db.models import Value as V
|
||||
from django.http.request import HttpRequest
|
||||
from sentry_sdk.hub import Hub
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.models import Tenant
|
||||
|
@ -28,7 +29,12 @@ def get_tenant_for_request(request: HttpRequest) -> Tenant:
|
|||
def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"""Context Processor that injects tenant object into every template"""
|
||||
tenant = getattr(request, "tenant", DEFAULT_TENANT)
|
||||
trace = ""
|
||||
span = Hub.current.scope.span
|
||||
if span:
|
||||
trace = span.to_traceparent()
|
||||
return {
|
||||
"tenant": tenant,
|
||||
"footer_links": CONFIG.y("footer_links"),
|
||||
"sentry_trace": trace,
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -28,7 +28,7 @@ require (
|
|||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
goauthentik.io/api v0.2021104.11
|
||||
goauthentik.io/api v0.2021104.17
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558
|
||||
|
|
4
go.sum
4
go.sum
|
@ -558,8 +558,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
goauthentik.io/api v0.2021104.11 h1:LqT0LM0e/RRrxPuo6Xl5uz3PCR5ytuE+YlNlfW9w0yU=
|
||||
goauthentik.io/api v0.2021104.11/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
|
||||
goauthentik.io/api v0.2021104.17 h1:NnfdoIlAekwPu+G7h7X/SGbWjWSypEy/pGQDD7/J+Vw=
|
||||
goauthentik.io/api v0.2021104.17/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
|
@ -47,7 +47,7 @@ type APIController struct {
|
|||
|
||||
// NewAPIController initialise new API Controller instance from URL and API token
|
||||
func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
rsp := sentry.StartSpan(context.TODO(), "authentik.outposts.init")
|
||||
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
|
||||
|
||||
config := api.NewConfiguration()
|
||||
config.Host = akURL.Host
|
||||
|
|
|
@ -107,6 +107,7 @@ func (ac *APIController) reconnectWS() {
|
|||
}
|
||||
} else {
|
||||
ac.wsIsReconnecting = false
|
||||
ac.wsBackoffMultiplier = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ak
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
@ -19,6 +20,8 @@ func NewTracingTransport(ctx context.Context, inner http.RoundTripper) *tracingT
|
|||
|
||||
func (tt *tracingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
span := sentry.StartSpan(tt.ctx, "authentik.go.http_request")
|
||||
r.Header.Set("sentry-trace", span.ToSentryTrace())
|
||||
span.Description = fmt.Sprintf("%s %s", r.Method, r.URL.String())
|
||||
span.SetTag("url", r.URL.String())
|
||||
span.SetTag("method", r.Method)
|
||||
defer span.Finish()
|
||||
|
|
|
@ -168,6 +168,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
|||
|
||||
func (a *Application) IsAllowlisted(r *http.Request) bool {
|
||||
for _, u := range a.UnauthenticatedRegex {
|
||||
a.log.WithField("regex", u.String()).WithField("url", r.URL.Path).Trace("Matching URL against allow list")
|
||||
if u.MatchString(r.URL.Path) {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package application
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
|
@ -18,6 +19,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig) sessions.Store {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rs.SetMaxLength(math.MaxInt64)
|
||||
if p.TokenValidity.IsSet() {
|
||||
t := p.TokenValidity.Get()
|
||||
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||
|
@ -27,14 +29,22 @@ func (a *Application) getStore(p api.ProxyOutpostConfig) sessions.Store {
|
|||
a.log.Info("using redis session backend")
|
||||
store = rs
|
||||
} else {
|
||||
cs := sessions.NewFilesystemStore(os.TempDir(), []byte(*p.CookieSecret))
|
||||
dir := os.TempDir()
|
||||
cs := sessions.NewFilesystemStore(dir, []byte(*p.CookieSecret))
|
||||
cs.Options.Domain = *p.CookieDomain
|
||||
// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7
|
||||
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
|
||||
// securecookie: the value is too long
|
||||
// when using OpenID Connect , since this can contain a large amount of extra information in the id_token
|
||||
|
||||
// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk
|
||||
cs.MaxLength(math.MaxInt64)
|
||||
if p.TokenValidity.IsSet() {
|
||||
t := p.TokenValidity.Get()
|
||||
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||
cs.Options.MaxAge = int(*t) + 1
|
||||
}
|
||||
a.log.Info("using filesystem session backend")
|
||||
a.log.WithField("dir", dir).Info("using filesystem session backend")
|
||||
store = cs
|
||||
}
|
||||
return store
|
||||
|
|
File diff suppressed because it is too large
Load diff
147
schema.yml
147
schema.yml
|
@ -3909,6 +3909,36 @@ paths:
|
|||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/events/events/per_month/:
|
||||
get:
|
||||
operationId: events_events_per_month_list
|
||||
description: Get the count of events per month
|
||||
parameters:
|
||||
- in: query
|
||||
name: action
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: query
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- events
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Coordinate'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/events/events/top_per_user/:
|
||||
get:
|
||||
operationId: events_events_top_per_user_list
|
||||
|
@ -3918,56 +3948,10 @@ paths:
|
|||
name: action
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: client_ip
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: context_authorized_app
|
||||
schema:
|
||||
type: string
|
||||
description: Context Authorized application
|
||||
- in: query
|
||||
name: context_model_app
|
||||
schema:
|
||||
type: string
|
||||
description: Context Model App
|
||||
- in: query
|
||||
name: context_model_name
|
||||
schema:
|
||||
type: string
|
||||
description: Context Model Name
|
||||
- in: query
|
||||
name: context_model_pk
|
||||
schema:
|
||||
type: string
|
||||
description: Context Model Primary Key
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
schema:
|
||||
type: string
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: tenant_name
|
||||
schema:
|
||||
type: string
|
||||
description: Tenant name
|
||||
- in: query
|
||||
name: top_n
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: username
|
||||
schema:
|
||||
type: string
|
||||
description: Username
|
||||
tags:
|
||||
- events
|
||||
security:
|
||||
|
@ -7539,6 +7523,7 @@ paths:
|
|||
- configuration_error
|
||||
- custom_
|
||||
- email_sent
|
||||
- flow_execution
|
||||
- impersonation_ended
|
||||
- impersonation_started
|
||||
- invitation_used
|
||||
|
@ -15395,6 +15380,14 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: user_verification
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- discouraged
|
||||
- preferred
|
||||
- required
|
||||
tags:
|
||||
- stages
|
||||
security:
|
||||
|
@ -19086,6 +19079,46 @@ components:
|
|||
- authentik.managed
|
||||
- authentik.core
|
||||
type: string
|
||||
AppleChallengeResponseRequest:
|
||||
type: object
|
||||
description: Pseudo class for plex response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: ak-flow-sources-oauth-apple
|
||||
AppleLoginChallenge:
|
||||
type: object
|
||||
description: Special challenge for apple-native authentication flow, which happens
|
||||
on the client.
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/ChallengeChoices'
|
||||
flow_info:
|
||||
$ref: '#/components/schemas/ContextualFlowInfo'
|
||||
component:
|
||||
type: string
|
||||
default: ak-flow-sources-oauth-apple
|
||||
response_errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ErrorDetail'
|
||||
client_id:
|
||||
type: string
|
||||
scope:
|
||||
type: string
|
||||
redirect_uri:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
required:
|
||||
- client_id
|
||||
- redirect_uri
|
||||
- scope
|
||||
- state
|
||||
- type
|
||||
Application:
|
||||
type: object
|
||||
description: Application Serializer
|
||||
|
@ -19200,6 +19233,8 @@ components:
|
|||
nullable: true
|
||||
description: Flow used by an authenticated user to configure this Stage.
|
||||
If empty, user will not be able to configure this stage.
|
||||
user_verification:
|
||||
$ref: '#/components/schemas/UserVerificationEnum'
|
||||
required:
|
||||
- component
|
||||
- meta_model_name
|
||||
|
@ -19224,6 +19259,8 @@ components:
|
|||
nullable: true
|
||||
description: Flow used by an authenticated user to configure this Stage.
|
||||
If empty, user will not be able to configure this stage.
|
||||
user_verification:
|
||||
$ref: '#/components/schemas/UserVerificationEnum'
|
||||
required:
|
||||
- name
|
||||
AuthenticatedSession:
|
||||
|
@ -20225,6 +20262,7 @@ components:
|
|||
ChallengeTypes:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AccessDeniedChallenge'
|
||||
- $ref: '#/components/schemas/AppleLoginChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorSMSChallenge'
|
||||
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||
|
@ -20246,6 +20284,7 @@ components:
|
|||
propertyName: component
|
||||
mapping:
|
||||
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
|
||||
ak-flow-sources-oauth-apple: '#/components/schemas/AppleLoginChallenge'
|
||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
|
||||
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallenge'
|
||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
|
||||
|
@ -21080,6 +21119,7 @@ components:
|
|||
- source_linked
|
||||
- impersonation_started
|
||||
- impersonation_ended
|
||||
- flow_execution
|
||||
- policy_execution
|
||||
- policy_exception
|
||||
- property_mapping_exception
|
||||
|
@ -21387,6 +21427,7 @@ components:
|
|||
- title
|
||||
FlowChallengeResponseRequest:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AppleChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
|
@ -21405,6 +21446,7 @@ components:
|
|||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
ak-flow-sources-oauth-apple: '#/components/schemas/AppleChallengeResponseRequest'
|
||||
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
|
||||
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
|
||||
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
|
||||
|
@ -22071,7 +22113,7 @@ components:
|
|||
additionalProperties: {}
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/User'
|
||||
- $ref: '#/components/schemas/GroupMember'
|
||||
readOnly: true
|
||||
single_use:
|
||||
type: boolean
|
||||
|
@ -22711,11 +22753,13 @@ components:
|
|||
oneOf:
|
||||
- $ref: '#/components/schemas/RedirectChallenge'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
- $ref: '#/components/schemas/AppleLoginChallenge'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
mapping:
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallenge'
|
||||
ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge'
|
||||
ak-flow-sources-oauth-apple: '#/components/schemas/AppleLoginChallenge'
|
||||
LoginMetrics:
|
||||
type: object
|
||||
description: Login Metrics per 1h
|
||||
|
@ -26565,6 +26609,8 @@ components:
|
|||
nullable: true
|
||||
description: Flow used by an authenticated user to configure this Stage.
|
||||
If empty, user will not be able to configure this stage.
|
||||
user_verification:
|
||||
$ref: '#/components/schemas/UserVerificationEnum'
|
||||
PatchedAuthenticatorDuoStageRequest:
|
||||
type: object
|
||||
description: AuthenticatorDuoStage Serializer
|
||||
|
@ -28992,6 +29038,7 @@ components:
|
|||
- github
|
||||
- google
|
||||
- openidconnect
|
||||
- okta
|
||||
- reddit
|
||||
- twitter
|
||||
type: string
|
||||
|
@ -31188,6 +31235,12 @@ components:
|
|||
- pk
|
||||
- source
|
||||
- user
|
||||
UserVerificationEnum:
|
||||
enum:
|
||||
- required
|
||||
- preferred
|
||||
- discouraged
|
||||
type: string
|
||||
UserWriteStage:
|
||||
type: object
|
||||
description: UserWriteStage Serializer
|
||||
|
|
|
@ -40,7 +40,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
sleep(1)
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image="beryju.org/oidc-test-client:latest",
|
||||
image="ghcr.io/beryju/oidc-test-client:latest",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
|
|
|
@ -40,7 +40,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
sleep(1)
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image="beryju.org/oidc-test-client:latest",
|
||||
image="ghcr.io/beryju/oidc-test-client:latest",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
|
@ -145,7 +145,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
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"], self.user.username)
|
||||
self.assertEqual(body["profile"]["name"], self.user.name)
|
||||
self.assertEqual(body["profile"]["email"], self.user.email)
|
||||
|
|
|
@ -39,7 +39,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
if force_post:
|
||||
metadata_url += f"&force_binding={SAML_BINDING_POST}"
|
||||
container = client.containers.run(
|
||||
image="beryju.org/saml-test-sp:latest",
|
||||
image="ghcr.io/beryju/saml-test-sp:latest",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
|
|
|
@ -229,7 +229,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
|
||||
def get_container_specs(self) -> Optional[dict[str, Any]]:
|
||||
return {
|
||||
"image": "beryju.org/oauth1-test-server:latest",
|
||||
"image": "ghcr.io/beryju/oauth1-test-server:latest",
|
||||
"detach": True,
|
||||
"network_mode": "host",
|
||||
"auto_remove": True,
|
||||
|
|
31
web/authentik/sources/okta.svg
Normal file
31
web/authentik/sources/okta.svg
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 400 134.7" style="enable-background:new 0 0 400 134.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#007DC1;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.3,33.8C22.5,33.8,0,56.3,0,84.1s22.5,50.3,50.3,50.3s50.3-22.5,50.3-50.3S78.1,33.8,50.3,33.8z
|
||||
M50.3,109.3c-13.9,0-25.2-11.3-25.2-25.2s11.3-25.2,25.2-25.2s25.2,11.3,25.2,25.2S64.2,109.3,50.3,109.3z"/>
|
||||
</g>
|
||||
<path class="st0" d="M138.7,101c0-4,4.8-5.9,7.6-3.1c12.6,12.8,33.4,34.8,33.5,34.9c0.3,0.3,0.6,0.8,1.8,1.2
|
||||
c0.5,0.2,1.3,0.2,2.2,0.2l22.7,0c4.1,0,5.3-4.7,3.4-7.1l-37.6-38.5l-2-2c-4.3-5.1-3.8-7.1,1.1-12.3L201.2,41c1.9-2.4,0.7-7-3.5-7
|
||||
h-20.6c-0.8,0-1.4,0-2,0.2c-1.2,0.4-1.7,0.8-2,1.2c-0.1,0.1-16.6,17.9-26.8,28.8c-2.8,3-7.8,1-7.8-3.1l0-57.1c0-2.9-2.4-4-4.3-4
|
||||
h-16.8c-2.9,0-4.3,1.9-4.3,3.6v126.6c0,2.9,2.4,3.7,4.4,3.7h16.8c2.6,0,4.3-1.9,4.3-3.8v-1.3V101z"/>
|
||||
<path class="st0" d="M275.9,129.6l-1.8-16.8c-0.2-2.3-2.4-3.9-4.7-3.5c-1.3,0.2-2.6,0.3-3.9,0.3c-13.4,0-24.3-10.5-25.1-23.8
|
||||
c0-0.4,0-0.9,0-1.4V63.8c0-2.7,2-4.9,4.7-4.9l22.5,0c1.6,0,4-1.4,4-4.3V38.7c0-3.1-2-4.7-3.8-4.7h-22.7c-2.6,0-4.7-1.9-4.8-4.5
|
||||
l0-25.5c0-1.6-1.2-4-4.3-4h-16.7c-2.1,0-4.1,1.3-4.1,3.9c0,0,0,81.5,0,81.9c0.7,27.2,23,48.9,50.3,48.9c2.3,0,4.5-0.2,6.7-0.5
|
||||
C274.6,133.9,276.2,131.9,275.9,129.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M397.1,108.5c-14.2,0-16.4-5.1-16.4-24.2c0-0.1,0-0.1,0-0.2l0-45.9c0-1.6-1.2-4.3-4.4-4.3h-16.8
|
||||
c-2.1,0-4.4,1.7-4.4,4.3l0,2.1c-7.3-4.2-15.8-6.6-24.8-6.6c-27.8,0-50.3,22.5-50.3,50.3c0,27.8,22.5,50.3,50.3,50.3
|
||||
c12.5,0,23.9-4.6,32.7-12.1c4.7,7.2,12.3,12,24.2,12.1c2,0,12.8,0.4,12.8-4.7v-17.9C400,110.2,398.8,108.5,397.1,108.5z
|
||||
M330.4,109.3c-13.9,0-25.2-11.3-25.2-25.2c0-13.9,11.3-25.2,25.2-25.2c13.9,0,25.2,11.3,25.2,25.2
|
||||
C355.5,98,344.2,109.3,330.4,109.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
1545
web/package-lock.json
generated
1545
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -45,24 +45,24 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.16.4",
|
||||
"@babel/plugin-transform-runtime": "^7.16.4",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-typescript": "^7.16.0",
|
||||
"@babel/core": "^7.16.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.16.5",
|
||||
"@babel/plugin-transform-runtime": "^7.16.5",
|
||||
"@babel/preset-env": "^7.16.5",
|
||||
"@babel/preset-typescript": "^7.16.5",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.10.4-1639076050",
|
||||
"@goauthentik/api": "^2021.10.4-1639516687",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.3.0",
|
||||
"@lingui/cli": "^3.13.0",
|
||||
"@lingui/core": "^3.13.0",
|
||||
"@lingui/detect-locale": "^3.13.0",
|
||||
"@lingui/macro": "^3.13.0",
|
||||
"@patternfly/patternfly": "^4.159.1",
|
||||
"@patternfly/patternfly": "^4.164.2",
|
||||
"@polymer/iron-form": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.0.6",
|
||||
"@rollup/plugin-node-resolve": "^13.1.1",
|
||||
"@rollup/plugin-replace": "^3.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.0",
|
||||
"@sentry/browser": "^6.16.1",
|
||||
|
@ -72,8 +72,8 @@
|
|||
"@types/chart.js": "^2.9.34",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.6.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"base64-js": "^1.5.1",
|
||||
|
@ -99,7 +99,7 @@
|
|||
"rollup-plugin-terser": "^7.0.2",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.5.3",
|
||||
"typescript": "^4.5.4",
|
||||
"webcomponent-qr-code": "^1.0.5",
|
||||
"yaml": "^1.10.2"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { getCookie } from "../utils";
|
|||
import { APIMiddleware } from "../elements/notifications/APIDrawer";
|
||||
import { MessageMiddleware } from "../elements/messages/Middleware";
|
||||
import { VERSION } from "../constants";
|
||||
import { getMetaContent } from "@sentry/tracing/dist/browser/browsertracing";
|
||||
|
||||
export class LoggingMiddleware implements Middleware {
|
||||
|
||||
|
@ -53,6 +54,7 @@ export const DEFAULT_CONFIG = new Configuration({
|
|||
basePath: process.env.AK_API_BASE_PATH + "/api/v3",
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie("authentik_csrf"),
|
||||
"sentry-trace": getMetaContent("sentry-trace") || "",
|
||||
},
|
||||
middleware: [
|
||||
new APIMiddleware(),
|
||||
|
|
|
@ -27,7 +27,10 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
|
|||
],
|
||||
tracesSampleRate: config.errorReporting.tracesSampleRate,
|
||||
environment: config.errorReporting.environment,
|
||||
beforeSend: async (event: Sentry.Event, hint: Sentry.EventHint): Promise<Sentry.Event | null> => {
|
||||
beforeSend: async (event: Sentry.Event, hint: Sentry.EventHint | undefined): Promise<Sentry.Event | null> => {
|
||||
if (!hint) {
|
||||
return event;
|
||||
}
|
||||
if (hint.originalException instanceof SentryIgnoredError) {
|
||||
return null;
|
||||
}
|
||||
|
@ -40,8 +43,13 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
|
|||
Sentry.setTag(TAG_SENTRY_CAPABILITIES, config.capabilities.join(","));
|
||||
if (window.location.pathname.includes("if/")) {
|
||||
// Get the interface name from URL
|
||||
const intf = window.location.pathname.replace(/.+if\/(.+)\//, "$1");
|
||||
Sentry.setTag(TAG_SENTRY_COMPONENT, `web/${intf}`);
|
||||
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
|
||||
let currentInterface = "unknown";
|
||||
if (pathMatches && pathMatches.length >= 2) {
|
||||
currentInterface = pathMatches[1];
|
||||
}
|
||||
Sentry.setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface}`);
|
||||
Sentry.configureScope((scope) => scope.setTransactionName(`authentik.web.if.${currentInterface}`));
|
||||
}
|
||||
if (config.errorReporting.sendPii && canDoPpi) {
|
||||
me().then(user => {
|
||||
|
|
|
@ -108,10 +108,11 @@ export class PageHeader extends LitElement {
|
|||
|
||||
renderIcon(): TemplateResult {
|
||||
if (this.icon) {
|
||||
if (this.iconImage) {
|
||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||
return html`<img class="pf-icon" src="${this.icon}" /> `;
|
||||
}
|
||||
return html`<i class=${this.icon}></i> `;
|
||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||
return html`<i class=${icon}></i> `;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
@ -132,7 +133,10 @@ export class PageHeader extends LitElement {
|
|||
</button>
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>${this.renderIcon()} ${this.header}</h1>
|
||||
<h1>
|
||||
${this.renderIcon()}
|
||||
<slot name="header"> ${this.header} </slot>
|
||||
</h1>
|
||||
${this.description ? html`<p>${this.description}</p>` : html``}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -18,6 +18,9 @@ export class AggregateCard extends LitElement {
|
|||
@property()
|
||||
headerLink?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
isCenter = true;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFCard, PFFlex, AKGlobal].concat([
|
||||
css`
|
||||
|
@ -59,7 +62,9 @@ export class AggregateCard extends LitElement {
|
|||
</div>
|
||||
${this.renderHeaderLink()}
|
||||
</div>
|
||||
<div class="pf-c-card__body center-value">${this.renderInner()}</div>
|
||||
<div class="pf-c-card__body ${this.isCenter ? "center-value" : ""}">
|
||||
${this.renderInner()}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
|
52
web/src/elements/charts/AdminModelPerDay.ts
Normal file
52
web/src/elements/charts/AdminModelPerDay.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { ChartData, Tick } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { Coordinate, EventActions, EventsApi } from "@goauthentik/api";
|
||||
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { AKChart } from "./Chart";
|
||||
|
||||
@customElement("ak-charts-admin-model-per-day")
|
||||
export class AdminModelPerDay extends AKChart<Coordinate[]> {
|
||||
@property()
|
||||
action: EventActions = EventActions.ModelCreated;
|
||||
|
||||
@property({ attribute: false })
|
||||
query?: { [key: string]: unknown } | undefined;
|
||||
|
||||
apiRequest(): Promise<Coordinate[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsPerMonthList({
|
||||
action: this.action,
|
||||
query: JSON.stringify(this.query || {}),
|
||||
});
|
||||
}
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600 / 24);
|
||||
return t`${ago} days ago`;
|
||||
}
|
||||
|
||||
getChartData(data: Coordinate[]): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: t`Objects created`,
|
||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ import { ArcElement, BarElement } from "chart.js";
|
|||
import { LinearScale, TimeScale } from "chart.js";
|
||||
import "chartjs-adapter-moment";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
|
@ -114,6 +116,13 @@ export abstract class AKChart<T> extends LitElement {
|
|||
];
|
||||
}
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600);
|
||||
return t`${ago} hours ago`;
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
|
@ -122,15 +131,8 @@ export abstract class AKChart<T> extends LitElement {
|
|||
type: "time",
|
||||
display: true,
|
||||
ticks: {
|
||||
callback: function (
|
||||
tickValue: string | number,
|
||||
index: number,
|
||||
ticks: Tick[],
|
||||
): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600);
|
||||
return `${ago} Hours ago`;
|
||||
callback: (tickValue: string | number, index: number, ticks: Tick[]) => {
|
||||
return this.timeTickCallback(tickValue, index, ticks);
|
||||
},
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 8,
|
||||
|
|
|
@ -187,6 +187,7 @@ export class DeleteBulkForm extends ModalButton {
|
|||
<p class="pf-c-title">
|
||||
${t`Are you sure you want to delete ${this.objects.length} ${this.objectLabel}?`}
|
||||
</p>
|
||||
<slot name="notice"></slot>
|
||||
</form>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
|
|
|
@ -102,6 +102,7 @@ export class APIDrawer extends LitElement {
|
|||
<div class="pf-c-notification-drawer__header">
|
||||
<div class="text">
|
||||
<h1 class="pf-c-notification-drawer__header-title">${t`API Requests`}</h1>
|
||||
<a href="/api/v3/" target="_blank">${t`Open API Browser`}</a>
|
||||
</div>
|
||||
<div class="pf-c-notification-drawer__header-action">
|
||||
<div class="pf-c-notification-drawer__header-action-close">
|
||||
|
|
|
@ -30,6 +30,19 @@ window.addEventListener("load", () => {
|
|||
})();
|
||||
});
|
||||
|
||||
export function paramURL(url: string, params?: { [key: string]: unknown }): string {
|
||||
let finalUrl = "#";
|
||||
finalUrl += url;
|
||||
if (params) {
|
||||
finalUrl += ";";
|
||||
finalUrl += encodeURIComponent(JSON.stringify(params));
|
||||
}
|
||||
return finalUrl;
|
||||
}
|
||||
export function navigate(url: string, params?: { [key: string]: unknown }): void {
|
||||
window.location.assign(paramURL(url, params));
|
||||
}
|
||||
|
||||
@customElement("ak-router-outlet")
|
||||
export class RouterOutlet extends LitElement {
|
||||
@property({ attribute: false })
|
||||
|
|
|
@ -32,6 +32,7 @@ import "../elements/LoadingOverlay";
|
|||
import { first } from "../utils";
|
||||
import "./FlowInspector";
|
||||
import "./access_denied/FlowAccessDenied";
|
||||
import "./sources/apple/AppleLoginInit";
|
||||
import "./sources/plex/PlexLoginInit";
|
||||
import "./stages/RedirectStage";
|
||||
import "./stages/authenticator_duo/AuthenticatorDuoStage";
|
||||
|
@ -321,6 +322,11 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
.host=${this as StageHost}
|
||||
.challenge=${this.challenge}
|
||||
></ak-flow-sources-plex>`;
|
||||
case "ak-flow-sources-oauth-apple":
|
||||
return html`<ak-flow-sources-oauth-apple
|
||||
.host=${this as StageHost}
|
||||
.challenge=${this.challenge}
|
||||
></ak-flow-sources-oauth-apple>`;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
79
web/src/flows/sources/apple/AppleLoginInit.ts
Normal file
79
web/src/flows/sources/apple/AppleLoginInit.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "../../../authentik.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
|
||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { AppleChallengeResponseRequest, AppleLoginChallenge } from "@goauthentik/api";
|
||||
|
||||
import "../../../elements/EmptyState";
|
||||
import { BaseStage } from "../../stages/base";
|
||||
|
||||
@customElement("ak-flow-sources-oauth-apple")
|
||||
export class AppleLoginInit extends BaseStage<AppleLoginChallenge, AppleChallengeResponseRequest> {
|
||||
@property({ type: Boolean })
|
||||
isModalShown = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle, AKGlobal];
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
const appleAuth = document.createElement("script");
|
||||
appleAuth.src =
|
||||
"https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js";
|
||||
appleAuth.type = "text/javascript";
|
||||
appleAuth.onload = () => {
|
||||
AppleID.auth.init({
|
||||
clientId: this.challenge?.clientId,
|
||||
scope: this.challenge.scope,
|
||||
redirectURI: this.challenge.redirectUri,
|
||||
state: this.challenge.state,
|
||||
usePopup: false,
|
||||
});
|
||||
AppleID.auth.signIn();
|
||||
this.isModalShown = true;
|
||||
};
|
||||
document.head.append(appleAuth);
|
||||
//Listen for authorization success
|
||||
document.addEventListener("AppleIDSignInOnSuccess", () => {
|
||||
//handle successful response
|
||||
});
|
||||
//Listen for authorization failures
|
||||
document.addEventListener("AppleIDSignInOnFailure", (error) => {
|
||||
console.warn(error);
|
||||
this.isModalShown = false;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">${t`Authenticating with Apple...`}</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
<form class="pf-c-form">
|
||||
<ak-empty-state ?loading="${true}"> </ak-empty-state>
|
||||
${!this.isModalShown
|
||||
? html`<button
|
||||
class="pf-c-button pf-m-primary pf-m-block"
|
||||
@click=${() => {
|
||||
AppleID.auth.signIn();
|
||||
}}
|
||||
>
|
||||
${t`Retry`}
|
||||
</button>`
|
||||
: html``}
|
||||
</form>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links"></ul>
|
||||
</footer>`;
|
||||
}
|
||||
}
|
14
web/src/flows/sources/apple/apple.d.ts
vendored
Normal file
14
web/src/flows/sources/apple/apple.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
declare namespace AppleID {
|
||||
const auth: AppleIDAuth;
|
||||
|
||||
class AppleIDAuth {
|
||||
init({
|
||||
clientId: string,
|
||||
scope: string,
|
||||
redirectURI: string,
|
||||
state: string,
|
||||
usePopup: boolean,
|
||||
}): void;
|
||||
async signIn(): Promise<void>;
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
|||
// byte arrays as expected by the spec.
|
||||
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
|
||||
this.challenge?.registration as PublicKeyCredentialCreationOptions,
|
||||
this.challenge?.registration.user.id,
|
||||
);
|
||||
|
||||
// request the authenticator(s) to create a new credential keypair.
|
||||
|
|
|
@ -8,15 +8,26 @@ export function b64RawEnc(buf: Uint8Array): string {
|
|||
return base64js.fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
export function u8arr(input: string): Uint8Array {
|
||||
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
|
||||
c.charCodeAt(0),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms items in the credentialCreateOptions generated on the server
|
||||
* into byte arrays expected by the navigator.credentials.create() call
|
||||
*/
|
||||
export function transformCredentialCreateOptions(
|
||||
credentialCreateOptions: PublicKeyCredentialCreationOptions,
|
||||
userId: string,
|
||||
): PublicKeyCredentialCreationOptions {
|
||||
const user = credentialCreateOptions.user;
|
||||
user.id = u8arr(b64enc(credentialCreateOptions.user.id as Uint8Array));
|
||||
// Because json can't contain raw bytes, the server base64-encodes the User ID
|
||||
// So to get the base64 encoded byte array, we first need to convert it to a regular
|
||||
// string, then a byte array, re-encode it and wrap that in an array.
|
||||
const stringId = decodeURIComponent(escape(window.atob(userId)));
|
||||
user.id = u8arr(b64enc(u8arr(stringId)));
|
||||
const challenge = u8arr(credentialCreateOptions.challenge.toString());
|
||||
|
||||
const transformedCredentialCreateOptions = Object.assign({}, credentialCreateOptions, {
|
||||
|
@ -63,12 +74,6 @@ export function transformNewAssertionForServer(newAssertion: PublicKeyCredential
|
|||
};
|
||||
}
|
||||
|
||||
function u8arr(input: string): Uint8Array {
|
||||
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
|
||||
c.charCodeAt(0),
|
||||
);
|
||||
}
|
||||
|
||||
export function transformCredentialRequestOptions(
|
||||
credentialRequestOptions: PublicKeyCredentialRequestOptions,
|
||||
): PublicKeyCredentialRequestOptions {
|
||||
|
|
|
@ -189,43 +189,37 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/if/user/" ?isAbsoluteLink=${true} ?highlight=${true}>
|
||||
<span slot="label">${t`User interface`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/overview">
|
||||
<span slot="label">${t`Overview`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/system-tasks">
|
||||
<span slot="label">${t`System Tasks`}</span>
|
||||
<ak-sidebar-item .expanded=${true}>
|
||||
<span slot="label">${t`Dashboards`}</span>
|
||||
<ak-sidebar-item path="/administration/overview">
|
||||
<span slot="label">${t`Overview`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/dashboard/users">
|
||||
<span slot="label">${t`Users`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/administration/system-tasks">
|
||||
<span slot="label">${t`System Tasks`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Resources`}</span>
|
||||
<span slot="label">${t`Applications`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/core/applications"
|
||||
.activeWhen=${[`^/core/applications/(?<slug>${SLUG_REGEX})$`]}
|
||||
>
|
||||
<span slot="label">${t`Applications`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item
|
||||
path="/core/sources"
|
||||
.activeWhen=${[`^/core/sources/(?<slug>${SLUG_REGEX})$`]}
|
||||
>
|
||||
<span slot="label">${t`Sources`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item
|
||||
path="/core/providers"
|
||||
.activeWhen=${[`^/core/providers/(?<id>${ID_REGEX})$`]}
|
||||
>
|
||||
<span slot="label">${t`Providers`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/core/tenants">
|
||||
<span slot="label">${t`Tenants`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Outposts`}</span>
|
||||
<ak-sidebar-item path="/outpost/outposts">
|
||||
<span slot="label">${t`Outposts`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/outpost/integrations">
|
||||
<span slot="label">${t`Integrations`}</span>
|
||||
<span slot="label">${t`Outpost Integrations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
|
@ -272,12 +266,9 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/flow/stages/prompts">
|
||||
<span slot="label">${t`Prompts`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/flow/stages/invitations">
|
||||
<span slot="label">${t`Invitations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Identity & Cryptography`}</span>
|
||||
<span slot="label">${t`Directory`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/identity/users"
|
||||
.activeWhen=${[`^/identity/users/(?<id>${ID_REGEX})$`]}
|
||||
|
@ -287,12 +278,27 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/identity/groups">
|
||||
<span slot="label">${t`Groups`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/crypto/certificates">
|
||||
<span slot="label">${t`Certificates`}</span>
|
||||
<ak-sidebar-item
|
||||
path="/core/sources"
|
||||
.activeWhen=${[`^/core/sources/(?<slug>${SLUG_REGEX})$`]}
|
||||
>
|
||||
<span slot="label">${t`Federation & Social login`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/core/tokens">
|
||||
<span slot="label">${t`Tokens & App passwords`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/flow/stages/invitations">
|
||||
<span slot="label">${t`Invitations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`System`}</span>
|
||||
<ak-sidebar-item path="/core/tenants">
|
||||
<span slot="label">${t`Tenants`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/crypto/certificates">
|
||||
<span slot="label">${t`Certificates`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -296,6 +296,10 @@ msgstr "Alternatively, if your current device has Duo installed, click on this l
|
|||
msgid "Always require consent"
|
||||
msgstr "Always require consent"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "An example setup can look like this:"
|
||||
msgstr "An example setup can look like this:"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Any HTML can be used."
|
||||
msgstr "Any HTML can be used."
|
||||
|
@ -344,6 +348,7 @@ msgstr "Application's display Name."
|
|||
msgid "Application(s)"
|
||||
msgstr "Application(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
|
@ -407,8 +412,12 @@ msgid "Assigned to application"
|
|||
msgstr "Assigned to application"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Assigned to {0} objects."
|
||||
msgstr "Assigned to {0} objects."
|
||||
msgid "Assigned to {0} object(s)."
|
||||
msgstr "Assigned to {0} object(s)."
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
#~ msgid "Assigned to {0} objects."
|
||||
#~ msgstr "Assigned to {0} objects."
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Attempted to log in as {0}"
|
||||
|
@ -433,6 +442,10 @@ msgstr "Audience"
|
|||
#~ msgid "Auth Type"
|
||||
#~ msgstr "Auth Type"
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Authenticating with Apple..."
|
||||
msgstr "Authenticating with Apple..."
|
||||
|
||||
#: src/flows/sources/plex/PlexLoginInit.ts
|
||||
msgid "Authenticating with Plex..."
|
||||
msgstr "Authenticating with Plex..."
|
||||
|
@ -445,6 +458,10 @@ msgstr "Authentication"
|
|||
msgid "Authentication Type"
|
||||
msgstr "Authentication Type"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Authentication URL"
|
||||
msgstr "Authentication URL"
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts
|
||||
|
@ -760,6 +777,10 @@ msgstr "Check status"
|
|||
msgid "Check the IP of the Kubernetes service, or"
|
||||
msgstr "Check the IP of the Kubernetes service, or"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Check the logs"
|
||||
msgstr "Check the logs"
|
||||
|
||||
#:
|
||||
#~ msgid "Check your Emails for a password reset link."
|
||||
#~ msgstr "Check your Emails for a password reset link."
|
||||
|
@ -1200,6 +1221,10 @@ msgstr "Create Token"
|
|||
msgid "Create User"
|
||||
msgstr "Create User"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Create a new application"
|
||||
msgstr "Create a new application"
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
msgid "Create group"
|
||||
msgstr "Create group"
|
||||
|
@ -1260,6 +1285,10 @@ msgstr "Customisation"
|
|||
msgid "DSA-SHA1"
|
||||
msgstr "DSA-SHA1"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Dashboards"
|
||||
msgstr "Dashboards"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
@ -1449,6 +1478,10 @@ msgstr "Digits"
|
|||
msgid "Direct querying, always returns the latest data, but slower than cached querying."
|
||||
msgstr "Direct querying, always returns the latest data, but slower than cached querying."
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Directory"
|
||||
msgstr "Directory"
|
||||
|
||||
#:
|
||||
#:
|
||||
#~ msgid "Disable"
|
||||
|
@ -1818,6 +1851,10 @@ msgstr "Expiry date"
|
|||
msgid "Explicit Consent"
|
||||
msgstr "Explicit Consent"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Explore integrations"
|
||||
msgstr "Explore integrations"
|
||||
|
||||
#: src/pages/flows/FlowViewPage.ts
|
||||
msgid "Export"
|
||||
msgstr "Export"
|
||||
|
@ -1855,7 +1892,6 @@ msgstr "External Applications which use authentik as Identity-Provider, utilizin
|
|||
msgid "External Host"
|
||||
msgstr "External Host"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "External host"
|
||||
|
@ -1866,6 +1902,10 @@ msgstr "External host"
|
|||
msgid "Failed Logins"
|
||||
msgstr "Failed Logins"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Failed Logins per day in the last month"
|
||||
msgstr "Failed Logins per day in the last month"
|
||||
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
msgid "Failed attempts before cancel"
|
||||
msgstr "Failed attempts before cancel"
|
||||
|
@ -1899,6 +1939,11 @@ msgstr "Failed to update {0}: {1}"
|
|||
msgid "Favicon"
|
||||
msgstr "Favicon"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
msgid "Federation & Social login"
|
||||
msgstr "Federation & Social login"
|
||||
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
msgid "Field"
|
||||
msgstr "Field"
|
||||
|
@ -1944,6 +1989,10 @@ msgstr "Flow"
|
|||
msgid "Flow Overview"
|
||||
msgstr "Flow Overview"
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "Flow execution"
|
||||
msgstr "Flow execution"
|
||||
|
||||
#: src/flows/FlowInspector.ts
|
||||
#: src/flows/FlowInspector.ts
|
||||
msgid "Flow inspector"
|
||||
|
@ -2270,8 +2319,8 @@ msgid "Identifier"
|
|||
msgstr "Identifier"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Identity & Cryptography"
|
||||
msgstr "Identity & Cryptography"
|
||||
#~ msgid "Identity & Cryptography"
|
||||
#~ msgstr "Identity & Cryptography"
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionDockerForm.ts
|
||||
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
|
||||
|
@ -2344,6 +2393,10 @@ msgstr "Import certificates of external providers or create certificates to sign
|
|||
msgid "In case you can't access any other method."
|
||||
msgstr "In case you can't access any other method."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com."
|
||||
msgstr "In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com."
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Inactive"
|
||||
msgstr "Inactive"
|
||||
|
@ -2366,8 +2419,8 @@ msgid "Integration key"
|
|||
msgstr "Integration key"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Integrations"
|
||||
msgstr "Integrations"
|
||||
#~ msgid "Integrations"
|
||||
#~ msgstr "Integrations"
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
|
@ -2689,6 +2742,10 @@ msgstr "Logins"
|
|||
msgid "Logins over the last 24 hours"
|
||||
msgstr "Logins over the last 24 hours"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Logins per day in the last month"
|
||||
msgstr "Logins per day in the last month"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Logo"
|
||||
msgstr "Logo"
|
||||
|
@ -2996,6 +3053,10 @@ msgstr "No Stages bound"
|
|||
msgid "No additional data available."
|
||||
msgstr "No additional data available."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "No additional setup is required."
|
||||
msgstr "No additional setup is required."
|
||||
|
||||
#: src/elements/forms/ModalForm.ts
|
||||
msgid "No form found"
|
||||
msgstr "No form found"
|
||||
|
@ -3143,6 +3204,10 @@ msgstr "Object field"
|
|||
msgid "Object uniqueness field"
|
||||
msgstr "Object uniqueness field"
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "Objects created"
|
||||
msgstr "Objects created"
|
||||
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
|
||||
|
@ -3161,6 +3226,10 @@ msgstr "Only fail the policy, don't invalidate user's password."
|
|||
msgid "Only send notification once, for example when sending a webhook into a chat channel."
|
||||
msgstr "Only send notification once, for example when sending a webhook into a chat channel."
|
||||
|
||||
#: src/elements/notifications/APIDrawer.ts
|
||||
msgid "Open API Browser"
|
||||
msgstr "Open API Browser"
|
||||
|
||||
#:
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Open application"
|
||||
|
@ -3210,8 +3279,8 @@ msgid "Optionally set the 'FriendlyName' value of the Assertion attribute."
|
|||
msgstr "Optionally set the 'FriendlyName' value of the Assertion attribute."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
|
@ -3239,6 +3308,10 @@ msgstr "Outdated outposts"
|
|||
msgid "Outpost Deployment Info"
|
||||
msgstr "Outpost Deployment Info"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Outpost Integrations"
|
||||
msgstr "Outpost Integrations"
|
||||
|
||||
#:
|
||||
#~ msgid "Outpost Service-connection"
|
||||
#~ msgstr "Outpost Service-connection"
|
||||
|
@ -3259,7 +3332,6 @@ msgstr "Outpost status"
|
|||
msgid "Outpost(s)"
|
||||
msgstr "Outpost(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Outposts"
|
||||
|
@ -3365,7 +3437,6 @@ msgid "Please enter your password"
|
|||
msgstr "Please enter your password"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Policies"
|
||||
|
@ -3584,6 +3655,10 @@ msgstr "Public key, acquired from https://www.google.com/recaptcha/intro/v3.html
|
|||
msgid "Publisher"
|
||||
msgstr "Publisher"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Quick actions"
|
||||
msgstr "Quick actions"
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||
msgstr "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||
|
@ -3761,14 +3836,18 @@ msgid "Reset Password"
|
|||
msgstr "Reset Password"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Resources"
|
||||
msgstr "Resources"
|
||||
#~ msgid "Resources"
|
||||
#~ msgstr "Resources"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/property-mappings/PropertyMappingTestForm.ts
|
||||
msgid "Result"
|
||||
msgstr "Result"
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Retry"
|
||||
msgstr "Retry"
|
||||
|
||||
#:
|
||||
#~ msgid "Retry Task"
|
||||
#~ msgstr "Retry Task"
|
||||
|
@ -4109,6 +4188,10 @@ msgstr "Set a custom HTTP-Basic Authentication header based on values from authe
|
|||
msgid "Set custom attributes using YAML or JSON."
|
||||
msgstr "Set custom attributes using YAML or JSON."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "Setup"
|
||||
msgstr "Setup"
|
||||
|
@ -4206,8 +4289,6 @@ msgstr "Source {0}"
|
|||
msgid "Source(s)"
|
||||
msgstr "Source(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Sources"
|
||||
msgstr "Sources"
|
||||
|
@ -4298,6 +4379,7 @@ msgstr "Stage(s)"
|
|||
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
#: src/pages/stages/captcha/CaptchaStageForm.ts
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
|
@ -4734,9 +4816,13 @@ msgstr "Sync status"
|
|||
msgid "Sync users"
|
||||
msgstr "Sync users"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "System Overview"
|
||||
msgstr "System Overview"
|
||||
#~ msgid "System Overview"
|
||||
#~ msgstr "System Overview"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
|
@ -4845,8 +4931,12 @@ msgid "The external URL you'll access the application at. Include any non-standa
|
|||
msgstr "The external URL you'll access the application at. Include any non-standard port."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
msgstr "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
#~ msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
#~ msgstr "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
|
||||
msgstr "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
|
||||
|
||||
#:
|
||||
#~ msgid "The following objects use {0}:"
|
||||
|
@ -5115,6 +5205,7 @@ msgid "UI settings"
|
|||
msgstr "UI settings"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "UID"
|
||||
msgstr "UID"
|
||||
|
||||
|
@ -5467,6 +5558,10 @@ msgstr "User interface"
|
|||
msgid "User matching mode"
|
||||
msgstr "User matching mode"
|
||||
|
||||
#: src/pages/admin-overview/UserDashboardPage.ts
|
||||
#~ msgid "User metrics"
|
||||
#~ msgstr "User metrics"
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "User object filter"
|
||||
msgstr "User object filter"
|
||||
|
@ -5475,10 +5570,30 @@ msgstr "User object filter"
|
|||
msgid "User password writeback"
|
||||
msgstr "User password writeback"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "User statistics"
|
||||
msgstr "User statistics"
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User status"
|
||||
msgstr "User status"
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification"
|
||||
msgstr "User verification"
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification is preferred if available, but not required."
|
||||
msgstr "User verification is preferred if available, but not required."
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification must occur."
|
||||
msgstr "User verification must occur."
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification should not occur."
|
||||
msgstr "User verification should not occur."
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "User was written to"
|
||||
msgstr "User was written to"
|
||||
|
@ -5529,6 +5644,7 @@ msgstr "Username"
|
|||
msgid "Username: Same as Text input, but checks for and prevents duplicate usernames."
|
||||
msgstr "Username: Same as Text input, but checks for and prevents duplicate usernames."
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
@ -5539,6 +5655,10 @@ msgstr "Users"
|
|||
msgid "Users added to this group will be superusers."
|
||||
msgstr "Users added to this group will be superusers."
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Users created per day in the last month"
|
||||
msgstr "Users created per day in the last month"
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
msgstr "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
|
@ -5651,6 +5771,10 @@ msgstr "Warning: Provider is not used by any Outpost."
|
|||
msgid "Warning: Provider not assigned to any application."
|
||||
msgstr "Warning: Provider not assigned to any application."
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Warning: You're about to delete the user you're logged in as ({0}). Proceed at your own risk."
|
||||
msgstr "Warning: You're about to delete the user you're logged in as ({0}). Proceed at your own risk."
|
||||
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Warning: authentik Domain is not configured, authentication will not work."
|
||||
msgstr "Warning: authentik Domain is not configured, authentication will not work."
|
||||
|
@ -5679,6 +5803,10 @@ msgstr "Webhook Mapping"
|
|||
msgid "Webhook URL"
|
||||
msgstr "Webhook URL"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Welcome, {name}."
|
||||
msgstr "Welcome, {name}."
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "When a user returns from the email successfully, their account will be activated."
|
||||
msgstr "When a user returns from the email successfully, their account will be activated."
|
||||
|
@ -5782,6 +5910,10 @@ msgstr "You're about to be redirect to the following URL."
|
|||
msgid "You're currently impersonating {0}. Click to stop."
|
||||
msgstr "You're currently impersonating {0}. Click to stop."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "app1 running on app1.example.com"
|
||||
msgstr "app1 running on app1.example.com"
|
||||
|
||||
#:
|
||||
#~ msgid "authentik Builtin Database"
|
||||
#~ msgstr "authentik Builtin Database"
|
||||
|
@ -5790,6 +5922,10 @@ msgstr "You're currently impersonating {0}. Click to stop."
|
|||
#~ msgid "authentik LDAP Backend"
|
||||
#~ msgstr "authentik LDAP Backend"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "authentik running on auth.example.com"
|
||||
msgstr "authentik running on auth.example.com"
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts
|
||||
msgid "connecting object will be deleted"
|
||||
msgstr "connecting object will be deleted"
|
||||
|
@ -5858,3 +5994,11 @@ msgstr "{0}, should be {1}"
|
|||
#: src/elements/forms/ConfirmationForm.ts
|
||||
msgid "{0}: {1}"
|
||||
msgstr "{0}: {1}"
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "{ago} days ago"
|
||||
msgstr "{ago} days ago"
|
||||
|
||||
#: src/elements/charts/Chart.ts
|
||||
msgid "{ago} hours ago"
|
||||
msgstr "{ago} hours ago"
|
||||
|
|
|
@ -300,6 +300,10 @@ msgstr "Sinon, si Duo est installé sur cet appareil, cliquez sur ce lien :"
|
|||
msgid "Always require consent"
|
||||
msgstr "Toujours exiger l'approbation"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "An example setup can look like this:"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Any HTML can be used."
|
||||
msgstr ""
|
||||
|
@ -348,6 +352,7 @@ msgstr "Nom d'affichage de l'application"
|
|||
msgid "Application(s)"
|
||||
msgstr "Application(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
|
@ -411,8 +416,12 @@ msgid "Assigned to application"
|
|||
msgstr "Assigné à l'application"
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Assigned to {0} objects."
|
||||
msgstr "Assigné à {0} objets"
|
||||
msgid "Assigned to {0} object(s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
#~ msgid "Assigned to {0} objects."
|
||||
#~ msgstr "Assigné à {0} objets"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Attempted to log in as {0}"
|
||||
|
@ -437,6 +446,10 @@ msgstr "Audience"
|
|||
#~ msgid "Auth Type"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Authenticating with Apple..."
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/sources/plex/PlexLoginInit.ts
|
||||
msgid "Authenticating with Plex..."
|
||||
msgstr "Authentification avec Plex..."
|
||||
|
@ -449,6 +462,10 @@ msgstr "Authentification"
|
|||
msgid "Authentication Type"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Authentication URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts
|
||||
|
@ -761,6 +778,10 @@ msgstr "Vérifier le statut"
|
|||
msgid "Check the IP of the Kubernetes service, or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Check the logs"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Check your Emails for a password reset link."
|
||||
#~ msgstr "Vérifiez vos courriels pour un lien de récupération de mot de passe."
|
||||
|
@ -1198,6 +1219,10 @@ msgstr "Créer un jeton"
|
|||
msgid "Create User"
|
||||
msgstr "Créer un utilisateu"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Create a new application"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
msgid "Create group"
|
||||
msgstr "Créer un groupe"
|
||||
|
@ -1258,6 +1283,10 @@ msgstr "Personalisation"
|
|||
msgid "DSA-SHA1"
|
||||
msgstr "DSA-SHA1"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Dashboards"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
@ -1439,6 +1468,10 @@ msgstr "Chiffres"
|
|||
msgid "Direct querying, always returns the latest data, but slower than cached querying."
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Disable"
|
||||
#~ msgstr "Désactiver"
|
||||
|
||||
|
@ -1804,6 +1837,10 @@ msgstr "Date d'expiration"
|
|||
msgid "Explicit Consent"
|
||||
msgstr "Approbation explicite"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Explore integrations"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowViewPage.ts
|
||||
msgid "Export"
|
||||
msgstr "Exporter"
|
||||
|
@ -1841,7 +1878,6 @@ msgstr "Applications externes qui utilisent authentik comme fournisseur d'identi
|
|||
msgid "External Host"
|
||||
msgstr "Hôte externe"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "External host"
|
||||
|
@ -1852,6 +1888,10 @@ msgstr "Hôte externe"
|
|||
msgid "Failed Logins"
|
||||
msgstr "Connexions échouées"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Failed Logins per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
msgid "Failed attempts before cancel"
|
||||
msgstr "Échecs avant annulation"
|
||||
|
@ -1885,6 +1925,11 @@ msgstr "Impossible de mettre à jour {0} : {1}"
|
|||
msgid "Favicon"
|
||||
msgstr "Favicon"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
msgid "Federation & Social login"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
msgid "Field"
|
||||
msgstr "Champ"
|
||||
|
@ -1929,6 +1974,10 @@ msgstr "Flux"
|
|||
msgid "Flow Overview"
|
||||
msgstr "Aperçu du flux"
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "Flow execution"
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/FlowInspector.ts
|
||||
#: src/flows/FlowInspector.ts
|
||||
msgid "Flow inspector"
|
||||
|
@ -2253,8 +2302,8 @@ msgid "Identifier"
|
|||
msgstr "Identifiant"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Identity & Cryptography"
|
||||
msgstr "Identité et chiffrement"
|
||||
#~ msgid "Identity & Cryptography"
|
||||
#~ msgstr "Identité et chiffrement"
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionDockerForm.ts
|
||||
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
|
||||
|
@ -2327,6 +2376,10 @@ msgstr "Importer les certificats des fournisseurs externes ou créer des certifi
|
|||
msgid "In case you can't access any other method."
|
||||
msgstr "Au cas où aucune autre méthode ne soit disponible."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Inactive"
|
||||
msgstr "Inactif"
|
||||
|
@ -2349,8 +2402,8 @@ msgid "Integration key"
|
|||
msgstr "Clé d'intégration"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Integrations"
|
||||
msgstr "Intégrations"
|
||||
#~ msgid "Integrations"
|
||||
#~ msgstr "Intégrations"
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
|
@ -2668,6 +2721,10 @@ msgstr "Connexions"
|
|||
msgid "Logins over the last 24 hours"
|
||||
msgstr "Connexions ces dernières 24 heures"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Logins per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Logo"
|
||||
msgstr "Logo"
|
||||
|
@ -2974,6 +3031,10 @@ msgstr "Aucune étape liée"
|
|||
msgid "No additional data available."
|
||||
msgstr "Aucune donnée additionnelle disponible."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "No additional setup is required."
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/ModalForm.ts
|
||||
msgid "No form found"
|
||||
msgstr "Aucun formulaire trouvé"
|
||||
|
@ -3119,6 +3180,10 @@ msgstr "Champ d'objet"
|
|||
msgid "Object uniqueness field"
|
||||
msgstr "Champ d'unicité de l'objet"
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "Objects created"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr "Durée d'expiration du consentement (Format : hours=1;minutes=2;seconds=3)."
|
||||
|
@ -3137,6 +3202,10 @@ msgstr "Faire simplement échouer la politique sans invalider le mot de passe ut
|
|||
msgid "Only send notification once, for example when sending a webhook into a chat channel."
|
||||
msgstr "Envoyer une seule fois la notification, par exemple lors de l'envoi d'un webhook dans un canal de discussion."
|
||||
|
||||
#: src/elements/notifications/APIDrawer.ts
|
||||
msgid "Open API Browser"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr "Ouvrir l'appication"
|
||||
|
||||
|
@ -3185,8 +3254,8 @@ msgid "Optionally set the 'FriendlyName' value of the Assertion attribute."
|
|||
msgstr "Indiquer la valeur \"FriendlyName\" de l'attribut d'assertion (optionnel)"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr "Indiquer votre domaine parent (optionnel), si vous souhaitez que l'authentification et l'autorisation soient réalisés au niveau du domaine. Si vous exécutez des applications sur app1.domain.tld, app2.domain.tld, indiquez ici \"domain.tld\"."
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr "Indiquer votre domaine parent (optionnel), si vous souhaitez que l'authentification et l'autorisation soient réalisés au niveau du domaine. Si vous exécutez des applications sur app1.domain.tld, app2.domain.tld, indiquez ici \"domain.tld\"."
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
|
@ -3213,6 +3282,10 @@ msgstr "Avant-postes périmés"
|
|||
msgid "Outpost Deployment Info"
|
||||
msgstr "Info de déploiement de l'avant-poste"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Outpost Integrations"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Outpost Service-connection"
|
||||
#~ msgstr "Connexion de service de l'avant-poste"
|
||||
|
||||
|
@ -3231,7 +3304,6 @@ msgstr "Statut de l'avant-poste"
|
|||
msgid "Outpost(s)"
|
||||
msgstr "Avant-poste(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Outposts"
|
||||
|
@ -3337,7 +3409,6 @@ msgid "Please enter your password"
|
|||
msgstr "Veuillez saisir votre mot de passe"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Policies"
|
||||
|
@ -3552,6 +3623,10 @@ msgstr "Clé publique, obtenue depuis https://www.google.com/recaptcha/intro/v3.
|
|||
msgid "Publisher"
|
||||
msgstr "Éditeur"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Quick actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||
msgstr "REDÉMARRER redémarre le flux depuis le début, en gardant le contexte du flux."
|
||||
|
@ -3732,14 +3807,18 @@ msgid "Reset Password"
|
|||
msgstr "Réinitialiser le mot de passe"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Resources"
|
||||
msgstr "Ressources"
|
||||
#~ msgid "Resources"
|
||||
#~ msgstr "Ressources"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/property-mappings/PropertyMappingTestForm.ts
|
||||
msgid "Result"
|
||||
msgstr "Résultat"
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Retry Task"
|
||||
#~ msgstr "Réessayer la tâche"
|
||||
|
||||
|
@ -4072,6 +4151,10 @@ msgstr "Définir un en-tête d'authentification HTTP-Basic personnalisé basé s
|
|||
msgid "Set custom attributes using YAML or JSON."
|
||||
msgstr "Définissez des attributs personnalisés via YAML ou JSON."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "Setup"
|
||||
msgstr ""
|
||||
|
@ -4168,8 +4251,6 @@ msgstr "Source {0}"
|
|||
msgid "Source(s)"
|
||||
msgstr "Source(s)"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Sources"
|
||||
msgstr "Sources"
|
||||
|
@ -4258,6 +4339,7 @@ msgstr "Étape(s)"
|
|||
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
#: src/pages/stages/captcha/CaptchaStageForm.ts
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
|
@ -4689,9 +4771,13 @@ msgstr "Synchroniser les statuts"
|
|||
msgid "Sync users"
|
||||
msgstr "Synchroniser les utilisateurs"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "System"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "System Overview"
|
||||
msgstr "Vue d'ensemble du système"
|
||||
#~ msgid "System Overview"
|
||||
#~ msgstr "Vue d'ensemble du système"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
|
@ -4799,8 +4885,12 @@ msgid "The external URL you'll access the application at. Include any non-standa
|
|||
msgstr "L'URL externe par laquelle vous accéderez à l'application. Incluez un port non-standard si besoin."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
msgstr "L'URL externe sur laquelle vous vous authentifierez. Cela peut être le même domaine qu'authentik."
|
||||
#~ msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
#~ msgstr "L'URL externe sur laquelle vous vous authentifierez. Cela peut être le même domaine qu'authentik."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "The following objects use {0}:"
|
||||
#~ msgstr "Les objets suivants utilisent {0} :"
|
||||
|
@ -5056,6 +5146,7 @@ msgid "UI settings"
|
|||
msgstr "Paramètres d'UI"
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "UID"
|
||||
msgstr "UID"
|
||||
|
||||
|
@ -5405,6 +5496,10 @@ msgstr "Interface utilisateur"
|
|||
msgid "User matching mode"
|
||||
msgstr "Mode de correspondance utilisateur"
|
||||
|
||||
#: src/pages/admin-overview/UserDashboardPage.ts
|
||||
#~ msgid "User metrics"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "User object filter"
|
||||
msgstr "Filtre des objets utilisateur"
|
||||
|
@ -5413,10 +5508,30 @@ msgstr "Filtre des objets utilisateur"
|
|||
msgid "User password writeback"
|
||||
msgstr "Réécriture du mot de passe utilisateur"
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "User statistics"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User status"
|
||||
msgstr "Statut utilisateur"
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification is preferred if available, but not required."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification must occur."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification should not occur."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "User was written to"
|
||||
msgstr "L'utilisateur a été écrit vers "
|
||||
|
@ -5467,6 +5582,7 @@ msgstr "Nom d'utilisateur"
|
|||
msgid "Username: Same as Text input, but checks for and prevents duplicate usernames."
|
||||
msgstr "Nom d'utilisateur : Identique à la saisie de texte, mais vérifie et empêche les noms d'utilisateur en double."
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
@ -5477,6 +5593,10 @@ msgstr "Utilisateurs"
|
|||
msgid "Users added to this group will be superusers."
|
||||
msgstr "Les utilisateurs ajoutés à ce groupe seront des super-utilisateurs."
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Users created per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
msgstr "Les utilisateurs de ce groupe peuvent effectuer des recherches. Si aucun groupe n'est sélectionné, aucune recherche LDAP n'est autorisée."
|
||||
|
@ -5589,6 +5709,10 @@ msgstr ""
|
|||
msgid "Warning: Provider not assigned to any application."
|
||||
msgstr "Avertissement : le fournisseur n'est assigné à aucune application."
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Warning: You're about to delete the user you're logged in as ({0}). Proceed at your own risk."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Warning: authentik Domain is not configured, authentication will not work."
|
||||
msgstr "Avertissement : le domaine d'authentik n'est pas configuré, l'authentification ne fonctionnera pas."
|
||||
|
@ -5617,6 +5741,10 @@ msgstr "Mapping Webhook"
|
|||
msgid "Webhook URL"
|
||||
msgstr "URL Webhoo"
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Welcome, {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "When a user returns from the email successfully, their account will be activated."
|
||||
msgstr "Lorsqu'un utilisateur revient de l'e-mail avec succès, son compte sera activé."
|
||||
|
@ -5718,12 +5846,20 @@ msgstr "Vous allez être redirigé vers l'URL suivante."
|
|||
msgid "You're currently impersonating {0}. Click to stop."
|
||||
msgstr "Vous êtes en train de vous faire passer pour {0}. Cliquez pour arrêter."
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "app1 running on app1.example.com"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "authentik Builtin Database"
|
||||
#~ msgstr "Base de données intégrée à authentik"
|
||||
|
||||
#~ msgid "authentik LDAP Backend"
|
||||
#~ msgstr "Backend LDAP authentik"
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "authentik running on auth.example.com"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts
|
||||
msgid "connecting object will be deleted"
|
||||
msgstr "L'objet connecté sera supprimé"
|
||||
|
@ -5792,3 +5928,11 @@ msgstr "{0}, devrait être {1}"
|
|||
#: src/elements/forms/ConfirmationForm.ts
|
||||
msgid "{0}: {1}"
|
||||
msgstr "{0} : {1}"
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "{ago} days ago"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/charts/Chart.ts
|
||||
msgid "{ago} hours ago"
|
||||
msgstr ""
|
||||
|
|
|
@ -296,6 +296,10 @@ msgstr ""
|
|||
msgid "Always require consent"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "An example setup can look like this:"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Any HTML can be used."
|
||||
msgstr ""
|
||||
|
@ -344,6 +348,7 @@ msgstr ""
|
|||
msgid "Application(s)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/applications/ApplicationListPage.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
|
@ -403,9 +408,13 @@ msgid "Assigned to application"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Assigned to {0} objects."
|
||||
msgid "Assigned to {0} object(s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
#~ msgid "Assigned to {0} objects."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
msgid "Attempted to log in as {0}"
|
||||
msgstr ""
|
||||
|
@ -429,6 +438,10 @@ msgstr ""
|
|||
#~ msgid "Auth Type"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Authenticating with Apple..."
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/sources/plex/PlexLoginInit.ts
|
||||
msgid "Authenticating with Plex..."
|
||||
msgstr ""
|
||||
|
@ -441,6 +454,10 @@ msgstr ""
|
|||
msgid "Authentication Type"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Authentication URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts
|
||||
|
@ -756,6 +773,10 @@ msgstr ""
|
|||
msgid "Check the IP of the Kubernetes service, or"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Check the logs"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Check your Emails for a password reset link."
|
||||
#~ msgstr ""
|
||||
|
@ -1194,6 +1215,10 @@ msgstr ""
|
|||
msgid "Create User"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Create a new application"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/ServiceAccountForm.ts
|
||||
msgid "Create group"
|
||||
msgstr ""
|
||||
|
@ -1254,6 +1279,10 @@ msgstr ""
|
|||
msgid "DSA-SHA1"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Dashboards"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
@ -1441,6 +1470,10 @@ msgstr ""
|
|||
msgid "Direct querying, always returns the latest data, but slower than cached querying."
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#~ msgid "Disable"
|
||||
|
@ -1810,6 +1843,10 @@ msgstr ""
|
|||
msgid "Explicit Consent"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Explore integrations"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/FlowViewPage.ts
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
@ -1847,7 +1884,6 @@ msgstr ""
|
|||
msgid "External Host"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "External host"
|
||||
|
@ -1858,6 +1894,10 @@ msgstr ""
|
|||
msgid "Failed Logins"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Failed Logins per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
msgid "Failed attempts before cancel"
|
||||
msgstr ""
|
||||
|
@ -1891,6 +1931,11 @@ msgstr ""
|
|||
msgid "Favicon"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
msgid "Federation & Social login"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
@ -1936,6 +1981,10 @@ msgstr ""
|
|||
msgid "Flow Overview"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "Flow execution"
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/FlowInspector.ts
|
||||
#: src/flows/FlowInspector.ts
|
||||
msgid "Flow inspector"
|
||||
|
@ -2262,8 +2311,8 @@ msgid "Identifier"
|
|||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Identity & Cryptography"
|
||||
msgstr ""
|
||||
#~ msgid "Identity & Cryptography"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionDockerForm.ts
|
||||
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
|
||||
|
@ -2336,6 +2385,10 @@ msgstr ""
|
|||
msgid "In case you can't access any other method."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
|
@ -2358,8 +2411,8 @@ msgid "Integration key"
|
|||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Integrations"
|
||||
msgstr ""
|
||||
#~ msgid "Integrations"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/tokens/TokenForm.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
|
@ -2679,6 +2732,10 @@ msgstr ""
|
|||
msgid "Logins over the last 24 hours"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Logins per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Logo"
|
||||
msgstr ""
|
||||
|
@ -2986,6 +3043,10 @@ msgstr ""
|
|||
msgid "No additional data available."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "No additional setup is required."
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/ModalForm.ts
|
||||
msgid "No form found"
|
||||
msgstr ""
|
||||
|
@ -3133,6 +3194,10 @@ msgstr ""
|
|||
msgid "Object uniqueness field"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "Objects created"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
|
||||
msgstr ""
|
||||
|
@ -3151,6 +3216,10 @@ msgstr ""
|
|||
msgid "Only send notification once, for example when sending a webhook into a chat channel."
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/notifications/APIDrawer.ts
|
||||
msgid "Open API Browser"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Open application"
|
||||
#~ msgstr ""
|
||||
|
@ -3200,8 +3269,8 @@ msgid "Optionally set the 'FriendlyName' value of the Assertion attribute."
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr ""
|
||||
#~ msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/flows/BoundStagesList.ts
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
|
@ -3229,6 +3298,10 @@ msgstr ""
|
|||
msgid "Outpost Deployment Info"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Outpost Integrations"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Outpost Service-connection"
|
||||
#~ msgstr ""
|
||||
|
@ -3249,7 +3322,6 @@ msgstr ""
|
|||
msgid "Outpost(s)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Outposts"
|
||||
|
@ -3355,7 +3427,6 @@ msgid "Please enter your password"
|
|||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
msgid "Policies"
|
||||
|
@ -3574,6 +3645,10 @@ msgstr ""
|
|||
msgid "Publisher"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Quick actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/flows/StageBindingForm.ts
|
||||
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||
msgstr ""
|
||||
|
@ -3751,14 +3826,18 @@ msgid "Reset Password"
|
|||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "Resources"
|
||||
msgstr ""
|
||||
#~ msgid "Resources"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/property-mappings/PropertyMappingTestForm.ts
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
|
||||
#: src/flows/sources/apple/AppleLoginInit.ts
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "Retry Task"
|
||||
#~ msgstr ""
|
||||
|
@ -4099,6 +4178,10 @@ msgstr ""
|
|||
msgid "Set custom attributes using YAML or JSON."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
msgid "Setup"
|
||||
msgstr ""
|
||||
|
@ -4196,8 +4279,6 @@ msgstr ""
|
|||
msgid "Source(s)"
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Sources"
|
||||
msgstr ""
|
||||
|
@ -4288,6 +4369,7 @@ msgstr ""
|
|||
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
|
||||
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
#: src/pages/stages/captcha/CaptchaStageForm.ts
|
||||
#: src/pages/stages/consent/ConsentStageForm.ts
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
|
@ -4724,10 +4806,14 @@ msgstr ""
|
|||
msgid "Sync users"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "System Overview"
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
msgid "System"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#~ msgid "System Overview"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
msgid "System Tasks"
|
||||
|
@ -4835,7 +4921,11 @@ msgid "The external URL you'll access the application at. Include any non-standa
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
#~ msgid "The external URL you'll authenticate at. Can be the same domain as authentik."
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
|
@ -5095,6 +5185,7 @@ msgid "UI settings"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "UID"
|
||||
msgstr ""
|
||||
|
||||
|
@ -5447,6 +5538,10 @@ msgstr ""
|
|||
msgid "User matching mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/UserDashboardPage.ts
|
||||
#~ msgid "User metrics"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "User object filter"
|
||||
msgstr ""
|
||||
|
@ -5455,10 +5550,30 @@ msgstr ""
|
|||
msgid "User password writeback"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "User statistics"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "User status"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification is preferred if available, but not required."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification must occur."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
|
||||
msgid "User verification should not occur."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/utils.ts
|
||||
msgid "User was written to"
|
||||
msgstr ""
|
||||
|
@ -5509,6 +5624,7 @@ msgstr ""
|
|||
msgid "Username: Same as Text input, but checks for and prevents duplicate usernames."
|
||||
msgstr ""
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
@ -5519,6 +5635,10 @@ msgstr ""
|
|||
msgid "Users added to this group will be superusers."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/DashboardUserPage.ts
|
||||
msgid "Users created per day in the last month"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/ldap/LDAPProviderForm.ts
|
||||
msgid "Users in the selected group can do search queries. If no group is selected, no LDAP Searches are allowed."
|
||||
msgstr ""
|
||||
|
@ -5631,6 +5751,10 @@ msgstr ""
|
|||
msgid "Warning: Provider not assigned to any application."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "Warning: You're about to delete the user you're logged in as ({0}). Proceed at your own risk."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/outposts/OutpostListPage.ts
|
||||
msgid "Warning: authentik Domain is not configured, authentication will not work."
|
||||
msgstr ""
|
||||
|
@ -5659,6 +5783,10 @@ msgstr ""
|
|||
msgid "Webhook URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/admin-overview/AdminOverviewPage.ts
|
||||
msgid "Welcome, {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "When a user returns from the email successfully, their account will be activated."
|
||||
msgstr ""
|
||||
|
@ -5760,6 +5888,10 @@ msgstr ""
|
|||
msgid "You're currently impersonating {0}. Click to stop."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "app1 running on app1.example.com"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#~ msgid "authentik Builtin Database"
|
||||
#~ msgstr ""
|
||||
|
@ -5768,6 +5900,10 @@ msgstr ""
|
|||
#~ msgid "authentik LDAP Backend"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
msgid "authentik running on auth.example.com"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/forms/DeleteForm.ts
|
||||
msgid "connecting object will be deleted"
|
||||
msgstr ""
|
||||
|
@ -5836,3 +5972,11 @@ msgstr ""
|
|||
#: src/elements/forms/ConfirmationForm.ts
|
||||
msgid "{0}: {1}"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/charts/AdminModelPerDay.ts
|
||||
msgid "{ago} days ago"
|
||||
msgstr ""
|
||||
|
||||
#: src/elements/charts/Chart.ts
|
||||
msgid "{ago} hours ago"
|
||||
msgstr ""
|
||||
|
|
|
@ -2,15 +2,19 @@ import { t } from "@lingui/macro";
|
|||
|
||||
import { CSSResult, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import AKGlobal from "../../authentik.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
import { me } from "../../api/Users";
|
||||
import "../../elements/PageHeader";
|
||||
import "../../elements/cards/AggregatePromiseCard";
|
||||
import "../../elements/charts/AdminLoginsChart";
|
||||
import { paramURL } from "../../elements/router/RouterOutlet";
|
||||
import "./TopApplicationsTable";
|
||||
import "./cards/AdminStatusCard";
|
||||
import "./cards/BackupStatusCard";
|
||||
|
@ -31,6 +35,7 @@ export class AdminOverviewPage extends LitElement {
|
|||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFList,
|
||||
AKGlobal,
|
||||
css`
|
||||
.row-divider {
|
||||
|
@ -51,11 +56,18 @@ export class AdminOverviewPage extends LitElement {
|
|||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html` <ak-page-header
|
||||
icon=""
|
||||
header=${t`System Overview`}
|
||||
description=${t`General system status`}
|
||||
>
|
||||
return html`<ak-page-header icon="" header="" description=${t`General system status`}>
|
||||
<span slot="header">
|
||||
${until(
|
||||
me().then((user) => {
|
||||
let name = user.user.username;
|
||||
if (user.user.name !== "") {
|
||||
name = user.user.name;
|
||||
}
|
||||
return t`Welcome, ${name}.`;
|
||||
}),
|
||||
)}
|
||||
</span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
|
@ -64,11 +76,33 @@ export class AdminOverviewPage extends LitElement {
|
|||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-infrastructure"
|
||||
header=${t`Policies`}
|
||||
headerLink="#/policy/policies"
|
||||
icon="fa fa-share"
|
||||
header=${t`Quick actions`}
|
||||
.isCenter=${false}
|
||||
>
|
||||
<ak-admin-status-chart-policy></ak-admin-status-chart-policy>
|
||||
<ul class="pf-c-list">
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
href=${paramURL("/core/applications", {
|
||||
createForm: true,
|
||||
})}
|
||||
>${t`Create a new application`}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pf-u-mb-xl" href=${paramURL("/events/log")}
|
||||
>${t`Check the logs`}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
href="https://goauthentik.io/integrations/"
|
||||
>${t`Explore integrations`}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
|
|
86
web/src/pages/admin-overview/DashboardUserPage.ts
Normal file
86
web/src/pages/admin-overview/DashboardUserPage.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "../../authentik.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
import { EventActions } from "@goauthentik/api";
|
||||
|
||||
import "../../elements/PageHeader";
|
||||
import "../../elements/cards/AggregatePromiseCard";
|
||||
import "../../elements/charts/AdminModelPerDay";
|
||||
|
||||
@customElement("ak-admin-dashboard-users")
|
||||
export class DashboardUserPage extends LitElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFList,
|
||||
AKGlobal,
|
||||
css`
|
||||
.row-divider {
|
||||
margin-top: -4px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.graph-container {
|
||||
height: 20em;
|
||||
}
|
||||
.big-graph-container {
|
||||
height: 35em;
|
||||
}
|
||||
.card-container {
|
||||
max-height: 10em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ak-page-header icon="pf-icon pf-icon-user" header=${t`User statistics`}>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Users created per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day
|
||||
.query=${{
|
||||
context__model__app: "authentik_core",
|
||||
context__model__model_name: "user",
|
||||
}}
|
||||
>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col row-divider">
|
||||
<hr />
|
||||
</div>
|
||||
<!-- row 2 -->
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Logins per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day action=${EventActions.Login}>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Failed Logins per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day action=${EventActions.LoginFailed}>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
</div>
|
||||
</section> `;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue