Merge pull request #941 from goauthentik/authenticated-sessions

Session management
This commit is contained in:
Jens L 2021-05-30 15:12:49 +02:00 committed by GitHub
commit fb6242d2d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 754 additions and 50 deletions

View File

@ -45,6 +45,7 @@ uvicorn = {extras = ["standard"],version = "*"}
webauthn = "*" webauthn = "*"
xmlsec = "*" xmlsec = "*"
duo-client = "*" duo-client = "*"
ua-parser = "*"
[requires] [requires]
python_version = "3.9" python_version = "3.9"

148
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "eb043e24ba05d5d78459a973fe0cd7c37dad1cca90431f68b6df773247c58cbb" "sha256": "4fa1ad681762c867a95410074f31ac5d00119e187e0f38982cd59fdf301cccf5"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -56,6 +56,7 @@
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.7.4.post0" "version": "==3.7.4.post0"
}, },
"aioredis": { "aioredis": {
@ -70,6 +71,7 @@
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2", "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb" "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.0.6" "version": "==5.0.6"
}, },
"asgiref": { "asgiref": {
@ -77,6 +79,7 @@
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.3.4" "version": "==3.3.4"
}, },
"async-timeout": { "async-timeout": {
@ -84,6 +87,7 @@
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
], ],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"attrs": { "attrs": {
@ -91,6 +95,7 @@
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0" "version": "==21.2.0"
}, },
"autobahn": { "autobahn": {
@ -98,6 +103,7 @@
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac", "sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03" "sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
], ],
"markers": "python_version >= '3.7'",
"version": "==21.3.1" "version": "==21.3.1"
}, },
"automat": { "automat": {
@ -116,24 +122,26 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:40ccb6ec2d7e5e4d250d630a245aae7aa1fcd43c3519e9808b444f083ca4014a", "sha256:1d24c6d1f5db4b52bb29f1dfe13fd3e9d95d9fa4634b0638a096f5a884173cde",
"sha256:e6fa13cd8f16a6c222104ab17e0439e24b6974f60e7af113a38a80f252457cb0" "sha256:8ee8766813864796be6c87ad762c6da4bfef603977931854a38f49fe4db06495"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.83" "version": "==1.17.84"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:55d450b6bf0df642809fe88a6840d90dab6b6ad5ff3dccaa1faf9e085dfd864a", "sha256:75e1397b80aa8757a26636b949eebd20b3cf67e8f1ed80dc01170907e06ea45d",
"sha256:b36c14cfe208969ee9f658b645cfc718c1700c593313787a3fd59b335f7a6e2c" "sha256:bc59eb748fcb07835613ebea6dcc2600ae1a8be0fae30e40b9c1e81b73262296"
], ],
"version": "==1.20.83" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.84"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001", "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff" "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
], ],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2" "version": "==4.2.2"
}, },
"cbor2": { "cbor2": {
@ -152,6 +160,7 @@
"sha256:f0058d33b5eaffb176d6190d175a5391f13362f165881deea2b99e63b66ecf55", "sha256:f0058d33b5eaffb176d6190d175a5391f13362f165881deea2b99e63b66ecf55",
"sha256:f5df0ad8c16f7992bf24e5c9a53f03a11a990fd18253c3c335315bd25a34f832" "sha256:f5df0ad8c16f7992bf24e5c9a53f03a11a990fd18253c3c335315bd25a34f832"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.3.0" "version": "==5.3.0"
}, },
"celery": { "celery": {
@ -244,6 +253,7 @@
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0" "version": "==4.0.0"
}, },
"click": { "click": {
@ -251,6 +261,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2" "version": "==7.1.2"
}, },
"click-didyoumean": { "click-didyoumean": {
@ -310,6 +321,7 @@
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f", "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393" "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.0.2" "version": "==3.0.2"
}, },
"defusedxml": { "defusedxml": {
@ -358,11 +370,11 @@
}, },
"django-otp": { "django-otp": {
"hashes": [ "hashes": [
"sha256:75a815747a0542cc5442e3a6396dfd272c49a0866bee2149ac57ecc36ddd3961", "sha256:01b5888f0bde5125e139433aacb947e52d5c406fa56c9db43c3e8d75b5c323c4",
"sha256:cc657a0e7266cda6ab42f861bdc3840ed24f7e441bc7f249916174dd1a6375a0" "sha256:0d56dd2a7fbb6ee6e54557e036ca64add0bd3596f471794bad673b7637d5e935"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.5" "version": "==1.0.6"
}, },
"django-prometheus": { "django-prometheus": {
"hashes": [ "hashes": [
@ -440,6 +452,7 @@
"hashes": [ "hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2" "version": "==0.18.2"
}, },
"geoip2": { "geoip2": {
@ -455,6 +468,7 @@
"sha256:044d81b1e58012f8ebc71cc134e191c1fa312f543f1fbc99973afe28c25e3228", "sha256:044d81b1e58012f8ebc71cc134e191c1fa312f543f1fbc99973afe28c25e3228",
"sha256:b3ca7a8ff9ab3bdefee3ad5aefb11fc6485423767eee016f5942d8e606ca23fb" "sha256:b3ca7a8ff9ab3bdefee3ad5aefb11fc6485423767eee016f5942d8e606ca23fb"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.30.1" "version": "==1.30.1"
}, },
"gunicorn": { "gunicorn": {
@ -470,6 +484,7 @@
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
], ],
"markers": "python_version >= '3.6'",
"version": "==0.12.0" "version": "==0.12.0"
}, },
"hiredis": { "hiredis": {
@ -516,6 +531,7 @@
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.0" "version": "==2.0.0"
}, },
"httptools": { "httptools": {
@ -564,6 +580,7 @@
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"jmespath": { "jmespath": {
@ -571,6 +588,7 @@
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.0" "version": "==0.10.0"
}, },
"jsonschema": { "jsonschema": {
@ -585,6 +603,7 @@
"sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d", "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d",
"sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a" "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.1.0" "version": "==5.1.0"
}, },
"kubernetes": { "kubernetes": {
@ -597,7 +616,10 @@
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91", "sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57" "sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
], ],
"index": "pypi", "index": "pypi",
@ -659,6 +681,7 @@
"hashes": [ "hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" "sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.3" "version": "==2.0.3"
}, },
"msgpack": { "msgpack": {
@ -734,6 +757,7 @@
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.1.0" "version": "==5.1.0"
}, },
"oauthlib": { "oauthlib": {
@ -741,6 +765,7 @@
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0" "version": "==3.1.0"
}, },
"packaging": { "packaging": {
@ -756,6 +781,7 @@
"sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa", "sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa",
"sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d" "sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.1" "version": "==0.10.1"
}, },
"prompt-toolkit": { "prompt-toolkit": {
@ -763,6 +789,7 @@
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
], ],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.18" "version": "==3.0.18"
}, },
"psycopg2-binary": { "psycopg2-binary": {
@ -808,15 +835,37 @@
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pyasn1-modules": { "pyasn1-modules": {
"hashes": [ "hashes": [
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"
], ],
"version": "==0.2.8" "version": "==0.2.8"
}, },
@ -825,6 +874,7 @@
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20" "version": "==2.20"
}, },
"pycryptodome": { "pycryptodome": {
@ -868,6 +918,7 @@
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.0.2" "version": "==2.0.2"
}, },
"pyjwt": { "pyjwt": {
@ -890,12 +941,14 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pyrsistent": { "pyrsistent": {
"hashes": [ "hashes": [
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.17.3" "version": "==0.17.3"
}, },
"python-dateutil": { "python-dateutil": {
@ -903,6 +956,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1" "version": "==2.8.1"
}, },
"python-dotenv": { "python-dotenv": {
@ -959,6 +1013,7 @@
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.5.3" "version": "==3.5.3"
}, },
"requests": { "requests": {
@ -966,12 +1021,14 @@
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1" "version": "==2.25.1"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.0" "version": "==1.3.0"
@ -1012,6 +1069,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0" "version": "==1.16.0"
}, },
"sqlparse": { "sqlparse": {
@ -1019,6 +1077,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.4.1" "version": "==0.4.1"
}, },
"structlog": { "structlog": {
@ -1074,6 +1133,7 @@
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8", "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb" "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
], ],
"markers": "python_version >= '3.6'",
"version": "==21.2.1" "version": "==21.2.1"
}, },
"typing-extensions": { "typing-extensions": {
@ -1084,11 +1144,20 @@
], ],
"version": "==3.10.0.0" "version": "==3.10.0.0"
}, },
"ua-parser": {
"hashes": [
"sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a",
"sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"
],
"index": "pypi",
"version": "==0.10.0"
},
"uritemplate": { "uritemplate": {
"hashes": [ "hashes": [
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"urllib3": { "urllib3": {
@ -1133,6 +1202,7 @@
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.0.0" "version": "==5.0.0"
}, },
"watchgod": { "watchgod": {
@ -1162,6 +1232,7 @@
"sha256:3e2bf58191d4619b161389a95bdce84ce9e0b24eb8107e7e590db682c2d0ca81", "sha256:3e2bf58191d4619b161389a95bdce84ce9e0b24eb8107e7e590db682c2d0ca81",
"sha256:abf306dc6351dcef07f4d40453037e51cc5d9da2ef60d0fc5d0fe3bcda255372" "sha256:abf306dc6351dcef07f4d40453037e51cc5d9da2ef60d0fc5d0fe3bcda255372"
], ],
"markers": "python_version >= '3.6'",
"version": "==1.0.1" "version": "==1.0.1"
}, },
"websockets": { "websockets": {
@ -1193,20 +1264,20 @@
}, },
"xmlsec": { "xmlsec": {
"hashes": [ "hashes": [
"sha256:17d2e66d4e3e601d210eed936b53c3eb44cddaef62f60b5c6ad5c18e948d926c", "sha256:23f209260b37bdc2fd96af837494c47dd1e67964f077442b63acd83c0f62e212",
"sha256:2bc1b871b49d6580779805a4a1c2d835e834a2fa614fe40cf71931d11a8279cf", "sha256:4fb38ab0bf3e47cbae136119674a869e09d61c939b510350f369c8ac46087373",
"sha256:52eded125c0d1ab72125105ef061370c6b06ab9bd37e29a61bc2f8a61205bae4", "sha256:705ab5b848afdf3a5c78b1322276054c885f44dc51601e14cb883a9c86cbe20f",
"sha256:72af9a5a747a5fe6e425d2be10daa43d18307dbe03498df3820fc3cd93daa148", "sha256:843d10bba4c480609da74ee11fff1ee0fc1c12821c656979f12a7a4ecb043e03",
"sha256:806855d505da24aeb77758a6f373b1473e5ed63bdbe346af90cc6d2b053e4716", "sha256:86d54b93f8278e2f0c504d0744e39a483c1c7ce9993f2ca70184cc7770faa982",
"sha256:8746dd992aaec06ed8ff1615f4a8e2a32258e8af38f9a9f8acf3ee1fb34a5da6", "sha256:8922fba55a060ee81de4a7f5efc593c5bf121047763aecf0eead02e061c9d2db",
"sha256:9d52b2b15d42292725e4f9d8a5b040e39cba0a9cd58059ac951e7310d6340bb9", "sha256:c7b49d4fce83186b89f7ce6cec765245d36a70d0acc2f3ed0ba95c735b3667da",
"sha256:b380f3ebc042f71afab057632481d06e06f1ba4f90047d91ca92612a7d3d487b", "sha256:cd2eaaff7f31784a07dd99ce81fa767313df3ba1834faa4143ee2c07000cac7a",
"sha256:be0f475edd8e9c98f57449c97839f6a81946e79e4cccb81e4b5196a2cc40e044", "sha256:dea5bef9b5830c36ccb7a68a0d94d49eaea4d03fbbd04179652bf661b7e6e30f",
"sha256:bf3c62d154f2222caf56d897ddfd53fd0aef560d5a2202447d90e015301a0a10", "sha256:eadff662d89c80db409c69d82eb3e695e16d4a5e8ab56b5b22670a54e9c6ff20",
"sha256:fe6a5f05aba3ff47e105a308482b68f8b0fd80656eb1456a9c1e4de47d2c580f" "sha256:ee233d0bc27fb8f447ca2622b0de2ac2df45b8795f02ef263825912011fe4fe9"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.10" "version": "==1.3.11"
}, },
"yarl": { "yarl": {
"hashes": [ "hashes": [
@ -1248,6 +1319,7 @@
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
], ],
"markers": "python_version >= '3.6'",
"version": "==1.6.3" "version": "==1.6.3"
}, },
"zope.interface": { "zope.interface": {
@ -1304,6 +1376,7 @@
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.4.0" "version": "==5.4.0"
} }
}, },
@ -1317,16 +1390,18 @@
}, },
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", "sha256:3c9a2d84354185d13213ff2640ec03d39168dbcd13648abc84fb13ca3b2e2761",
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" "sha256:d66a600e1602736a0f24f725a511b0e50d12eb18f54b31ec276d2c26a0a62c6a"
], ],
"version": "==2.5.6" "markers": "python_version ~= '3.6'",
"version": "==2.5.7"
}, },
"attrs": { "attrs": {
"hashes": [ "hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0" "version": "==21.2.0"
}, },
"bandit": { "bandit": {
@ -1365,6 +1440,7 @@
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0" "version": "==4.0.0"
}, },
"click": { "click": {
@ -1372,6 +1448,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2" "version": "==7.1.2"
}, },
"colorama": { "colorama": {
@ -1445,6 +1522,7 @@
"sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
"sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
], ],
"markers": "python_version >= '3.4'",
"version": "==4.0.7" "version": "==4.0.7"
}, },
"gitpython": { "gitpython": {
@ -1452,6 +1530,7 @@
"sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135", "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135",
"sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e" "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.1.17" "version": "==3.1.17"
}, },
"idna": { "idna": {
@ -1473,6 +1552,7 @@
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
], ],
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==5.8.0" "version": "==5.8.0"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -1500,6 +1580,7 @@
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.6.0" "version": "==1.6.0"
}, },
"mccabe": { "mccabe": {
@ -1536,6 +1617,7 @@
"sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd", "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd",
"sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4" "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"
], ],
"markers": "python_version >= '2.6'",
"version": "==5.6.0" "version": "==5.6.0"
}, },
"pluggy": { "pluggy": {
@ -1543,6 +1625,7 @@
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1" "version": "==0.13.1"
}, },
"py": { "py": {
@ -1550,6 +1633,7 @@
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0" "version": "==1.10.0"
}, },
"pylint": { "pylint": {
@ -1580,6 +1664,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pytest": { "pytest": {
@ -1684,6 +1769,7 @@
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1" "version": "==2.25.1"
}, },
"requests-mock": { "requests-mock": {
@ -1707,6 +1793,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0" "version": "==1.16.0"
}, },
"smmap": { "smmap": {
@ -1714,6 +1801,7 @@
"sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
"sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
], ],
"markers": "python_version >= '3.5'",
"version": "==4.0.0" "version": "==4.0.0"
}, },
"stevedore": { "stevedore": {
@ -1721,6 +1809,7 @@
"sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee",
"sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.3.0" "version": "==3.3.0"
}, },
"toml": { "toml": {
@ -1728,6 +1817,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2" "version": "==0.10.2"
}, },
"urllib3": { "urllib3": {

View File

@ -11,6 +11,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.events.geo import GEOIP_READER
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
@ -18,6 +19,7 @@ class Capabilities(models.TextChoices):
"""Define capabilities which influence which APIs can/should be used""" """Define capabilities which influence which APIs can/should be used"""
CAN_SAVE_MEDIA = "can_save_media" CAN_SAVE_MEDIA = "can_save_media"
CAN_GEO_IP = "can_geo_ip"
class ConfigSerializer(PassiveSerializer): class ConfigSerializer(PassiveSerializer):
@ -41,6 +43,8 @@ class ConfigView(APIView):
deb_test = settings.DEBUG or settings.TEST deb_test = settings.DEBUG or settings.TEST
if path.ismount(settings.MEDIA_ROOT) or deb_test: if path.ismount(settings.MEDIA_ROOT) or deb_test:
caps.append(Capabilities.CAN_SAVE_MEDIA) caps.append(Capabilities.CAN_SAVE_MEDIA)
if GEOIP_READER:
caps.append(Capabilities.CAN_GEO_IP)
return caps return caps
@extend_schema(responses={200: ConfigSerializer(many=False)}) @extend_schema(responses={200: ConfigSerializer(many=False)})

View File

@ -12,6 +12,7 @@ from authentik.admin.api.workers import WorkerView
from authentik.api.v2.config import ConfigView from authentik.api.v2.config import ConfigView
from authentik.api.views import APIBrowserView from authentik.api.views import APIBrowserView
from authentik.core.api.applications import ApplicationViewSet from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
from authentik.core.api.groups import GroupViewSet from authentik.core.api.groups import GroupViewSet
from authentik.core.api.propertymappings import PropertyMappingViewSet from authentik.core.api.propertymappings import PropertyMappingViewSet
from authentik.core.api.providers import ProviderViewSet from authentik.core.api.providers import ProviderViewSet
@ -108,6 +109,7 @@ router = routers.DefaultRouter()
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
router.register("admin/apps", AppsViewSet, basename="apps") router.register("admin/apps", AppsViewSet, basename="apps")
router.register("core/authenticated_sessions", AuthenticatedSessionViewSet)
router.register("core/applications", ApplicationViewSet) router.register("core/applications", ApplicationViewSet)
router.register("core/groups", GroupViewSet) router.register("core/groups", GroupViewSet)
router.register("core/users", UserViewSet) router.register("core/users", UserViewSet)

View File

@ -0,0 +1,136 @@
"""AuthenticatedSessions API Viewset"""
from typing import Optional, TypedDict
from django_filters.rest_framework import DjangoFilterBackend
from geoip2.errors import GeoIP2Error
from guardian.utils import get_anonymous_user
from rest_framework import mixins
from rest_framework.fields import SerializerMethodField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.request import Request
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from ua_parser import user_agent_parser
from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER
class UserAgentDeviceDict(TypedDict):
"""User agent device"""
brand: str
family: str
model: str
class UserAgentOSDict(TypedDict):
"""User agent os"""
family: str
major: str
minor: str
patch: str
patch_minor: str
class UserAgentBrowserDict(TypedDict):
"""User agent browser"""
family: str
major: str
minor: str
patch: str
class UserAgentDict(TypedDict):
"""User agent details"""
device: UserAgentDeviceDict
os: UserAgentOSDict
user_agent: UserAgentBrowserDict
string: str
class GeoIPDict(TypedDict):
"""GeoIP Details"""
continent: str
country: str
lat: float
long: float
class AuthenticatedSessionSerializer(ModelSerializer):
"""AuthenticatedSession Serializer"""
current = SerializerMethodField()
user_agent = SerializerMethodField()
geo_ip = SerializerMethodField()
def get_current(self, instance: AuthenticatedSession) -> bool:
"""Check if session is currently active session"""
request: Request = self.context["request"]
return request._request.session.session_key == instance.session_key
def get_user_agent(self, instance: AuthenticatedSession) -> UserAgentDict:
"""Get parsed user agent"""
return user_agent_parser.Parse(instance.last_user_agent)
def get_geo_ip(
self, instance: AuthenticatedSession
) -> Optional[GeoIPDict]: # pragma: no cover
"""Get parsed user agent"""
if not GEOIP_READER:
return None
try:
city = GEOIP_READER.city(instance.last_ip)
return {
"continent": city.continent.code,
"country": city.country.iso_code,
"lat": city.location.latitude,
"long": city.location.longitude,
}
except (GeoIP2Error, ValueError):
return None
class Meta:
model = AuthenticatedSession
fields = [
"uuid",
"current",
"user_agent",
"geo_ip",
"user",
"last_ip",
"last_user_agent",
"last_used",
"expires",
]
class AuthenticatedSessionViewSet(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""AuthenticatedSession Viewset"""
queryset = AuthenticatedSession.objects.all()
serializer_class = AuthenticatedSessionSerializer
search_fields = ["user__username", "last_ip", "last_user_agent"]
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
ordering = ["user__username"]
filter_backends = [
DjangoFilterBackend,
OrderingFilter,
SearchFilter,
]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)

View File

@ -0,0 +1,86 @@
# Generated by Django 3.2.3 on 2021-05-29 22:14
import uuid
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.utils.timezone import now
import authentik.core.models
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
from django.contrib.sessions.backends.cache import KEY_PREFIX, SessionStore
from django.core.cache import cache
AuthenticatedSession = apps.get_model("authentik_core", "AuthenticatedSession")
User = apps.get_model("authentik_core", "user")
session_keys = cache.keys(KEY_PREFIX + "*")
for key in session_keys:
key = key.replace(KEY_PREFIX, "")
store = SessionStore(key)
data = store.load()
if data == {} or "_auth_user_id" not in data:
continue
if (
AuthenticatedSession.objects.using(db_alias)
.filter(session_key=key)
.exists()
):
continue
users = User.objects.using(db_alias).filter(pk=data.get("_auth_user_id"))
if not users.exists():
continue
AuthenticatedSession.objects.using(db_alias).create(
session_key=key,
user=users.first(),
expires=data.get("_session_expiry", now()),
)
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0021_alter_application_slug"),
]
operations = [
migrations.CreateModel(
name="AuthenticatedSession",
fields=[
(
"expires",
models.DateTimeField(
default=authentik.core.models.default_token_duration
),
),
("expiring", models.BooleanField(default=True)),
(
"uuid",
models.UUIDField(
default=uuid.uuid4, primary_key=True, serialize=False
),
),
("session_key", models.CharField(max_length=40)),
("last_ip", models.TextField()),
("last_user_agent", models.TextField(blank=True)),
("last_used", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"abstract": False,
},
),
migrations.RunPython(migrate_sessions),
]

View File

@ -28,6 +28,7 @@ from authentik.flows.challenge import Challenge
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.models import CreatedUpdatedModel, SerializerModel from authentik.lib.models import CreatedUpdatedModel, SerializerModel
from authentik.lib.utils.http import get_client_ip
from authentik.managed.models import ManagedModel from authentik.managed.models import ManagedModel
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
@ -452,3 +453,33 @@ class PropertyMapping(SerializerModel, ManagedModel):
verbose_name = _("Property Mapping") verbose_name = _("Property Mapping")
verbose_name_plural = _("Property Mappings") verbose_name_plural = _("Property Mappings")
class AuthenticatedSession(ExpiringModel):
"""Additional session class for authenticated users. Augments the standard django session
to achieve the following:
- Make it queryable by user
- Have a direct connection to user objects
- Allow users to view their own sessions and terminate them
- Save structured and well-defined information.
"""
uuid = models.UUIDField(default=uuid4, primary_key=True)
session_key = models.CharField(max_length=40)
user = models.ForeignKey(User, on_delete=models.CASCADE)
last_ip = models.TextField()
last_user_agent = models.TextField(blank=True)
last_used = models.DateTimeField(auto_now=True)
@staticmethod
def from_request(request: HttpRequest, user: User) -> "AuthenticatedSession":
"""Create a new session from a http request"""
return AuthenticatedSession(
session_key=request.session.session_key,
user=user,
last_ip=get_client_ip(request),
last_user_agent=request.META.get("HTTP_USER_AGENT", ""),
expires=request.session.get_expiry_date(),
)

View File

@ -1,9 +1,13 @@
"""authentik core signals""" """authentik core signals"""
from typing import TYPE_CHECKING
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.core.cache import cache from django.core.cache import cache
from django.core.signals import Signal from django.core.signals import Signal
from django.db.models import Model from django.db.models import Model
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.http.request import HttpRequest
from prometheus_client import Gauge from prometheus_client import Gauge
# Arguments: user: User, password: str # Arguments: user: User, password: str
@ -13,6 +17,9 @@ GAUGE_MODELS = Gauge(
"authentik_models", "Count of various objects", ["model_name", "app"] "authentik_models", "Count of various objects", ["model_name", "app"]
) )
if TYPE_CHECKING:
from authentik.core.models import User
@receiver(post_save) @receiver(post_save)
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -33,3 +40,23 @@ def post_save_application(sender: type[Model], instance, created: bool, **_):
# Also delete user application cache # Also delete user application cache
keys = cache.keys(user_app_cache_key("*")) keys = cache.keys(user_app_cache_key("*"))
cache.delete_many(keys) cache.delete_many(keys)
@receiver(user_logged_in)
# pylint: disable=unused-argument
def user_logged_in_session(sender, request: HttpRequest, user: "User", **_):
"""Create an AuthenticatedSession from request"""
from authentik.core.models import AuthenticatedSession
AuthenticatedSession.from_request(request, user).save()
@receiver(user_logged_out)
# pylint: disable=unused-argument
def user_logged_out_session(sender, request: HttpRequest, user: "User", **_):
"""Delete AuthenticatedSession if it exists"""
from authentik.core.models import AuthenticatedSession
AuthenticatedSession.objects.filter(
session_key=request.session.session_key
).delete()

View File

@ -0,0 +1,31 @@
"""Test AuthenticatedSessions API"""
from json import loads
from django.urls.base import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
class TestAuthenticatedSessionsAPI(APITestCase):
"""Test AuthenticatedSessions API"""
def setUp(self) -> None:
super().setUp()
self.user = User.objects.get(username="akadmin")
self.other_user = User.objects.create(username="normal-user")
def test_list(self):
"""Test session list endpoint"""
self.client.force_login(self.user)
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
self.assertEqual(response.status_code, 200)
def test_non_admin_list(self):
"""Test non-admin list"""
self.client.force_login(self.other_user)
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
self.assertEqual(response.status_code, 200)
body = loads(force_str(response.content))
self.assertEqual(body["pagination"]["count"], 1)

View File

@ -149,7 +149,7 @@ class Event(ExpiringModel):
request.session[SESSION_IMPERSONATE_USER] request.session[SESSION_IMPERSONATE_USER]
) )
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = get_client_ip(request) or "255.255.255.255" self.client_ip = get_client_ip(request)
# Apply GeoIP Data, when enabled # Apply GeoIP Data, when enabled
self.with_geoip() self.with_geoip()
# If there's no app set, we get it from the requests too # If there's no app set, we get it from the requests too
@ -158,7 +158,7 @@ class Event(ExpiringModel):
self.save() self.save()
return self return self
def with_geoip(self): def with_geoip(self): # pragma: no cover
"""Apply GeoIP Data, when enabled""" """Apply GeoIP Data, when enabled"""
if not GEOIP_READER: if not GEOIP_READER:
return return
@ -172,7 +172,7 @@ class Event(ExpiringModel):
} }
if response.city.name: if response.city.name:
self.context["geo"]["city"] = response.city.name self.context["geo"]["city"] = response.city.name
except GeoIP2Error as exc: except (GeoIP2Error, ValueError) as exc:
LOGGER.warning("Failed to add geoIP Data to event", exc=exc) LOGGER.warning("Failed to add geoIP Data to event", exc=exc)
def _set_prom_metrics(self): def _set_prom_metrics(self):

View File

@ -47,7 +47,7 @@ outposts:
authentik: authentik:
avatars: gravatar # gravatar or none avatars: gravatar # gravatar or none
geoip: "" geoip: "./GeoLite2-City.mmdb"
# Optionally add links to the footer on the login page # Optionally add links to the footer on the login page
footer_links: footer_links:
- name: Documentation - name: Documentation

View File

@ -5,9 +5,10 @@ from django.http import HttpRequest
OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP" OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP"
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips" USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
DEFAULT_IP = "255.255.255.255"
def _get_client_ip_from_meta(meta: dict[str, Any]) -> Optional[str]: def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers. """Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found""" Returns none if no IP Could be found"""
headers = ( headers = (
@ -19,7 +20,7 @@ def _get_client_ip_from_meta(meta: dict[str, Any]) -> Optional[str]:
if _header in meta: if _header in meta:
ips: list[str] = meta.get(_header).split(",") ips: list[str] = meta.get(_header).split(",")
return ips[0].strip() return ips[0].strip()
return None return DEFAULT_IP
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]: def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
@ -37,7 +38,7 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
return request.META[OUTPOST_REMOTE_IP_HEADER] return request.META[OUTPOST_REMOTE_IP_HEADER]
def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]: def get_client_ip(request: Optional[HttpRequest]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers. """Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found""" Returns none if no IP Could be found"""
if request: if request:
@ -45,4 +46,4 @@ def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]:
if override: if override:
return override return override
return _get_client_ip_from_meta(request.META) return _get_client_ip_from_meta(request.META)
return None return DEFAULT_IP

View File

@ -50,9 +50,7 @@ class PolicyEvaluator(BaseEvaluator):
"""Update context based on http request""" """Update context based on http request"""
# update website/docs/expressions/_objects.md # update website/docs/expressions/_objects.md
# update website/docs/expressions/_functions.md # update website/docs/expressions/_functions.md
self._context["ak_client_ip"] = ip_address( self._context["ak_client_ip"] = ip_address(get_client_ip(request))
get_client_ip(request) or "255.255.255.255"
)
self._context["http_request"] = request self._context["http_request"] = request
def handle_error(self, exc: Exception, expression_source: str): def handle_error(self, exc: Exception, expression_source: str):

View File

@ -30,7 +30,7 @@ class ReputationPolicy(Policy):
return "ak-policy-reputation-form" return "ak-policy-reputation-form"
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
remote_ip = get_client_ip(request.http_request) or "255.255.255.255" remote_ip = get_client_ip(request.http_request)
passing = True passing = True
if self.check_ip: if self.check_ip:
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)

View File

@ -17,7 +17,7 @@ LOGGER = get_logger()
def update_score(request: HttpRequest, username: str, amount: int): def update_score(request: HttpRequest, username: str, amount: int):
"""Update score for IP and User""" """Update score for IP and User"""
remote_ip = get_client_ip(request) or "255.255.255.255" remote_ip = get_client_ip(request)
# We only update the cache here, as its faster than writing to the DB # We only update the cache here, as its faster than writing to the DB
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)

View File

@ -36,7 +36,7 @@ class PolicyRequest:
self.obj = None self.obj = None
self.context = {} self.context = {}
def set_http_request(self, request: HttpRequest): def set_http_request(self, request: HttpRequest): # pragma: no cover
"""Load data from HTTP request, including geoip when enabled""" """Load data from HTTP request, including geoip when enabled"""
self.http_request = request self.http_request = request
if not GEOIP_READER: if not GEOIP_READER:
@ -47,7 +47,7 @@ class PolicyRequest:
return return
response = GEOIP_READER.city(client_ip) response = GEOIP_READER.city(client_ip)
self.context["geoip"] = response self.context["geoip"] = response
except GeoIP2Error as exc: except (GeoIP2Error, ValueError) as exc:
LOGGER.warning("failed to get geoip data", exc=exc) LOGGER.warning("failed to get geoip data", exc=exc)
def __str__(self): def __str__(self):

View File

@ -1520,6 +1520,114 @@ paths:
description: Bad request description: Bad request
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
/api/v2beta/core/authenticated_sessions/:
get:
operationId: core_authenticated_sessions_list
description: AuthenticatedSession Viewset
parameters:
- in: query
name: last_ip
schema:
type: string
- in: query
name: last_user_agent
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: user__username
schema:
type: string
tags:
- core
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedAuthenticatedSessionList'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/authenticated_sessions/{uuid}/:
get:
operationId: core_authenticated_sessions_retrieve
description: AuthenticatedSession Viewset
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this authenticated session.
required: true
tags:
- core
security:
- authentik: []
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatedSession'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: core_authenticated_sessions_destroy
description: AuthenticatedSession Viewset
parameters:
- in: path
name: uuid
schema:
type: string
format: uuid
description: A UUID string identifying this authenticated session.
required: true
tags:
- core
security:
- authentik: []
- cookieAuth: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/groups/: /api/v2beta/core/groups/:
get: get:
operationId: core_groups_list operationId: core_groups_list
@ -15474,6 +15582,90 @@ components:
If empty, user will not be able to configure this stage. If empty, user will not be able to configure this stage.
required: required:
- name - name
AuthenticatedSession:
type: object
description: AuthenticatedSession Serializer
properties:
uuid:
type: string
format: uuid
current:
type: boolean
readOnly: true
user_agent:
type: object
properties:
device:
type: object
properties:
brand:
type: string
family:
type: string
model:
type: string
os:
type: object
properties:
family:
type: string
major:
type: string
minor:
type: string
patch:
type: string
patch_minor:
type: string
user_agent:
type: object
properties:
family:
type: string
major:
type: string
minor:
type: string
patch:
type: string
string:
type: string
readOnly: true
geo_ip:
type: object
properties:
continent:
type: string
country:
type: string
lat:
type: number
format: float
long:
type: number
format: float
nullable: true
readOnly: true
user:
type: integer
last_ip:
type: string
last_user_agent:
type: string
last_used:
type: string
format: date-time
readOnly: true
expires:
type: string
format: date-time
required:
- current
- geo_ip
- last_ip
- last_used
- user
- user_agent
AuthenticatorDuoChallenge: AuthenticatorDuoChallenge:
type: object type: object
description: Duo Challenge description: Duo Challenge
@ -16013,6 +16205,7 @@ components:
CapabilitiesEnum: CapabilitiesEnum:
enum: enum:
- can_save_media - can_save_media
- can_geo_ip
type: string type: string
CaptchaChallenge: CaptchaChallenge:
type: object type: object
@ -18914,6 +19107,41 @@ components:
required: required:
- pagination - pagination
- results - results
PaginatedAuthenticatedSessionList:
type: object
properties:
pagination:
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
results:
type: array
items:
$ref: '#/components/schemas/AuthenticatedSession'
required:
- pagination
- results
PaginatedAuthenticatorDuoStageList: PaginatedAuthenticatorDuoStageList:
type: object type: object
properties: properties:

View File

@ -0,0 +1,60 @@
import { t } from "@lingui/macro";
import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client";
import { Table, TableColumn } from "../table/Table";
import "../forms/DeleteForm";
import { PAGE_SIZE } from "../../constants";
import { CoreApi, AuthenticatedSession } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-user-session-list")
export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
@property()
targetUser!: string;
apiEndpoint(page: number): Promise<AKResponse<AuthenticatedSession>> {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsList({
userUsername: this.targetUser,
ordering: this.order,
page: page,
pageSize: PAGE_SIZE,
});
}
order = "-expires";
columns(): TableColumn[] {
return [
new TableColumn(t`Last IP`, "last_ip"),
new TableColumn(t`Browser`, "user_agent"),
new TableColumn(t`Device`, "user_agent"),
new TableColumn(t`Expires`, "expires"),
new TableColumn(""),
];
}
row(item: AuthenticatedSession): TemplateResult[] {
return [
html`${item.lastIp}`,
html`${item.userAgent.userAgent?.family}`,
html`${item.userAgent.os?.family}`,
html`${item.expires?.toLocaleString()}`,
html`
<ak-forms-delete
.obj=${item}
objectLabel=${t`Session`}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreAuthenticatedSessionsDestroy({
uuid: item.uuid || "",
});
}}>
<button slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete Session`}
</button>
</ak-forms-delete>`,
];
}
}

View File

@ -16,7 +16,7 @@ export class UserConsentList extends Table<UserConsent> {
apiEndpoint(page: number): Promise<AKResponse<UserConsent>> { apiEndpoint(page: number): Promise<AKResponse<UserConsent>> {
return new CoreApi(DEFAULT_CONFIG).coreUserConsentList({ return new CoreApi(DEFAULT_CONFIG).coreUserConsentList({
user: this.userId, user: this.userId,
ordering: "expires", ordering: this.order,
page: page, page: page,
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
}); });

View File

@ -13,18 +13,19 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import AKGlobal from "../../authentik.css"; import AKGlobal from "../../authentik.css";
import "../../elements/forms/ModalForm";
import "../../elements/buttons/ActionButton"; import "../../elements/buttons/ActionButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/charts/UserChart";
import "../../elements/CodeMirror"; import "../../elements/CodeMirror";
import "../../elements/Tabs";
import "../../elements/events/ObjectChangelog"; import "../../elements/events/ObjectChangelog";
import "../../elements/user/UserConsentList"; import "../../elements/events/UserEvents";
import "../../elements/forms/ModalForm";
import "../../elements/oauth/UserCodeList"; import "../../elements/oauth/UserCodeList";
import "../../elements/oauth/UserRefreshList"; import "../../elements/oauth/UserRefreshList";
import "../../elements/charts/UserChart";
import "../../elements/PageHeader"; import "../../elements/PageHeader";
import "../../elements/events/UserEvents"; import "../../elements/Tabs";
import "../../elements/user/SessionList";
import "../../elements/user/UserConsentList";
import "./UserForm"; import "./UserForm";
import { CoreApi, User } from "authentik-api"; import { CoreApi, User } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
@ -176,6 +177,14 @@ export class UserViewPage extends LitElement {
</div> </div>
</div> </div>
</section> </section>
<section slot="page-sessions" data-tab-title="${t`Sessions`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-user-session-list targetUser=${this.user.username}>
</ak-user-session-list>
</div>
</div>
</section>
<section slot="page-events" data-tab-title="${t`User events`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> <section slot="page-events" data-tab-title="${t`User events`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__body"> <div class="pf-c-card__body">