Compare commits
137 Commits
version/20
...
version-20
Author | SHA1 | Date | |
---|---|---|---|
18778ce0d9 | |||
14973fb595 | |||
9171bd6d6f | |||
4e5eeacf0a | |||
d1d28722d2 | |||
a6e528d209 | |||
2c70301f56 | |||
07b9923bf6 | |||
8b3923200d | |||
3dcd67c1a3 | |||
2a9feafb90 | |||
580e88c6fc | |||
d82c01aa61 | |||
1af3357826 | |||
ed49d7824e | |||
378402fcf0 | |||
50f0c11c0b | |||
58712828a4 | |||
b2b9093c95 | |||
afa2afe1d4 | |||
5f58a4566c | |||
d616bdd5d6 | |||
5112ef9331 | |||
7a49377caf | |||
5b3941a425 | |||
c1ab5c5556 | |||
3282b34431 | |||
392d9bb10b | |||
82f6c515ea | |||
d67d5f73c5 | |||
799d186510 | |||
3983b7fbe4 | |||
d75284a587 | |||
71e4936dc3 | |||
9d3b6f7a4d | |||
003df44a34 | |||
a7598c6ee5 | |||
0891e43040 | |||
1f49aea48d | |||
499b52df6a | |||
b8a566f4a0 | |||
aa0e8edb8b | |||
0e35bb18c7 | |||
4a06ebf4f9 | |||
11584af425 | |||
a31da9e1d3 | |||
8d6d49834b | |||
2825710262 | |||
7346ccf2b7 | |||
57072dd6ce | |||
fec098a823 | |||
73950b72e5 | |||
b40afb9b7d | |||
1f783dfc01 | |||
7ccf8bcdc8 | |||
76131e40ec | |||
5955394c1d | |||
a8998a6356 | |||
dc75d7b7f0 | |||
34a191f216 | |||
299931985e | |||
b946fbf9e7 | |||
e20bb7d636 | |||
5db3409efc | |||
649db054a6 | |||
15d5b91642 | |||
e9abc25b92 | |||
dc930c0cdf | |||
464a1c0536 | |||
837d2f6fab | |||
8f00d73512 | |||
b75feab709 | |||
9c8433ec4d | |||
ef080900a4 | |||
10b45a8dea | |||
c43ac1f704 | |||
14d702450a | |||
0a1a2a035e | |||
ace777ebbe | |||
8a6879afa5 | |||
fdc7f14056 | |||
8be80aaf9d | |||
e476f2dda2 | |||
5d48cfab14 | |||
1f22f0e7bb | |||
ce082ead5e | |||
dd2cd09637 | |||
828fe07fca | |||
a074ea70e9 | |||
84ce2c1df2 | |||
8628595590 | |||
7b8e5c4272 | |||
caa5dc1d14 | |||
f328b21e89 | |||
52abd959eb | |||
a0cd17a257 | |||
32c5bf04b8 | |||
766c4873a0 | |||
240136154b | |||
78dd7b0341 | |||
0021a93952 | |||
67240fb9ad | |||
4add0bbe86 | |||
d2dd7d1366 | |||
476e57daa2 | |||
4eb8a0dcd1 | |||
de74f3ec1f | |||
ce98255607 | |||
53b9e5b93f | |||
7aeb390eac | |||
5df9ad63cf | |||
e4400476a2 | |||
ef3c01ec34 | |||
b136d3bc69 | |||
c34fcc73dc | |||
11b09c4ebd | |||
e32070ddeb | |||
33a8cea007 | |||
d01fd7cdb7 | |||
1770e42cbf | |||
2fed739be7 | |||
aa820b2b4d | |||
582d2eb5eb | |||
c5e2635903 | |||
cfe0a7a694 | |||
c579540473 | |||
35f2b06611 | |||
9c4f025d71 | |||
d8b8e8a5a3 | |||
ec34c3eb75 | |||
0554c94c53 | |||
e72881b2a9 | |||
4452ff171e | |||
39bdc3a9a9 | |||
33bb6edf8c | |||
2eb18ff5e6 | |||
aeb1b5e8f2 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.4.2
|
||||
current_version = 2021.4.6
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -18,11 +18,11 @@ jobs:
|
||||
- name: Building Docker Image
|
||||
run: docker build
|
||||
--no-cache
|
||||
-t beryju/authentik:2021.4.2
|
||||
-t beryju/authentik:2021.4.6
|
||||
-t beryju/authentik:latest
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik:2021.4.2
|
||||
run: docker push beryju/authentik:2021.4.6
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik:latest
|
||||
build-proxy:
|
||||
@ -48,11 +48,11 @@ jobs:
|
||||
cd outpost/
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/authentik-proxy:2021.4.2 \
|
||||
-t beryju/authentik-proxy:2021.4.6 \
|
||||
-t beryju/authentik-proxy:latest \
|
||||
-f proxy.Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik-proxy:2021.4.2
|
||||
run: docker push beryju/authentik-proxy:2021.4.6
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik-proxy:latest
|
||||
build-static:
|
||||
@ -72,11 +72,11 @@ jobs:
|
||||
cd web/
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/authentik-static:2021.4.2 \
|
||||
-t beryju/authentik-static:2021.4.6 \
|
||||
-t beryju/authentik-static:latest \
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/authentik-static:2021.4.2
|
||||
run: docker push beryju/authentik-static:2021.4.6
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/authentik-static:latest
|
||||
test-release:
|
||||
@ -110,5 +110,5 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
tagName: 2021.4.2
|
||||
tagName: 2021.4.6
|
||||
environment: beryjuorg-prod
|
||||
|
128
Pipfile.lock
generated
128
Pipfile.lock
generated
@ -116,18 +116,18 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:73bcd04f6f919e7f8acc27c9d83dab5aee22225fe624c028b2e1c7feaf771098",
|
||||
"sha256:c45e7d3aef8965ae1b42c9855c31ded19fbb38cfad0a34cc37dc880ded3672c2"
|
||||
"sha256:1e55df93aa47a84e2a12a639c7f145e16e6e9ef959542d69d5526d50d2e92692",
|
||||
"sha256:eab42daaaf68cdad5b112d31dcb0684162098f6558ba7b64156be44f993525fa"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.51"
|
||||
"version": "==1.17.54"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:ae45ea7451513373666b7571064c173d649e61fd3e8f413f0e1f1f9db26b3513",
|
||||
"sha256:c853d6c2321e2f2328282c7d49d7b1a06201826ba0e7049c6975ab5f22927ea8"
|
||||
"sha256:20a864fc6570ba11d52532c72c3ccabab5c71a9b4a9418601a313d56f1d2ce5b",
|
||||
"sha256:37ec76ea2df8609540ba6cb0fe360ae1c589d2e1ee91eb642fd767823f3fcedd"
|
||||
],
|
||||
"version": "==1.20.51"
|
||||
"version": "==1.20.54"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
@ -437,10 +437,10 @@
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:186fe2564634d67fbbb64f3daf8bc8c9cecbb2a7f535ed1a8a71795e50db8d87",
|
||||
"sha256:70b39558712826e41f65e5f05a8d879361deaf84df8883e5dd0ec3d0da6ab66e"
|
||||
"sha256:010f011c4e27d3d5eb01106fba6aac39d164842dfcd8709955c4638f5b11ccf8",
|
||||
"sha256:f30a672a64d91cc2e3137765d088c5deec26416246f7a9e956eaf69a8d7ed49c"
|
||||
],
|
||||
"version": "==1.28.1"
|
||||
"version": "==1.29.0"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
@ -1106,10 +1106,10 @@
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994",
|
||||
"sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246"
|
||||
"sha256:af1af6384bd7fb8208b06480f9be73d0295d965c4c073a5c95ea5b6661dccc18",
|
||||
"sha256:f3dfd791cad2799403e3c8051810a7ca6ee1d2e630e5d2a8f9649d892bdb3db6"
|
||||
],
|
||||
"version": "==0.3.7"
|
||||
"version": "==0.4.0"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
@ -1374,59 +1374,59 @@
|
||||
},
|
||||
"zope.interface": {
|
||||
"hashes": [
|
||||
"sha256:02d3535aa18e34ce97c58d241120b7554f7d1cf4f8002fc9675cc7e7745d20e8",
|
||||
"sha256:0378a42ec284b65706d9ef867600a4a31701a0d6773434e6537cfc744e3343f4",
|
||||
"sha256:07d289358a8c565ea09e426590dd1179f93cf5ac3dd17d43fcc4fc63c1a9d275",
|
||||
"sha256:0e6cdbdd94ae94d1433ab51f46a76df0f2cd041747c31baec1c1ffa4e76bd0c1",
|
||||
"sha256:11354fb8b8bdc5cdd66358ed4f1f0ce739d78ff6d215d33b8f3ae282258c0f11",
|
||||
"sha256:12588a46ae0a99f172c4524cbbc3bb870f32e0f8405e9fa11a5ef3fa3a808ad7",
|
||||
"sha256:16caa44a06f6b0b2f7626ced4b193c1ae5d09c1b49c9b4962c93ae8aa2134f55",
|
||||
"sha256:18c478b89b6505756f007dcf76a67224a23dcf0f365427742ed0c0473099caa4",
|
||||
"sha256:221b41442cf4428fcda7fc958c9721c916709e2a3a9f584edd70f1493a09a762",
|
||||
"sha256:26109c50ccbcc10f651f76277cfc05fba8418a907daccc300c9247f24b3158a2",
|
||||
"sha256:28d8157f8c77662a1e0796a7d3cfa8910289131d4b4dd4e10b2686ab1309b67b",
|
||||
"sha256:2c51689b7b40c7d9c7e8a678350e73dc647945a13b4e416e7a02bbf0c37bdb01",
|
||||
"sha256:2ec58e1e1691dde4fbbd97f8610de0f8f1b1a38593653f7d3b8e931b9cd6d67f",
|
||||
"sha256:416feb6500f7b6fc00d32271f6b8495e67188cb5eb51fc8e289b81fdf465a9cb",
|
||||
"sha256:520352b18adea5478bbf387e9c77910a914985671fe36bc5ef19fdcb67a854bc",
|
||||
"sha256:527415b5ca201b4add44026f70278fbc0b942cf0801a26ca5527cb0389b6151e",
|
||||
"sha256:54243053316b5eec92affe43bbace7c8cd946bc0974a4aa39ff1371df0677b22",
|
||||
"sha256:61b8454190b9cc87279232b6de28dee0bad040df879064bb2f0e505cda907918",
|
||||
"sha256:672668729edcba0f2ee522ab177fcad91c81cfce991c24d8767765e2637d3515",
|
||||
"sha256:67aa26097e194947d29f2b5a123830e03da1519bcce10cac034a51fcdb99c34f",
|
||||
"sha256:6e7305e42b5f54e5ccf51820de46f0a7c951ba7cb9e3f519e908545b0f5628d0",
|
||||
"sha256:7234ac6782ca43617de803735949f79b894f0c5d353fbc001d745503c69e6d1d",
|
||||
"sha256:7426bea25bdf92f00fa52c7b30fcd2a2f71c21cf007178971b1f248b6c2d3145",
|
||||
"sha256:74b331c5d5efdddf5bbd9e1f7d8cb91a0d6b9c4ba45ca3e9003047a84dca1a3b",
|
||||
"sha256:79b6db1a18253db86e9bf1e99fa829d60fd3fc7ac04f4451c44e4bdcf6756a42",
|
||||
"sha256:7d79cd354ae0a033ac7b86a2889c9e8bb0bb48243a6ed27fc5064ce49b842ada",
|
||||
"sha256:823d1b4a6a028b8327e64865e2c81a8959ae9f4e7c9c8e0eec814f4f9b36b362",
|
||||
"sha256:8715717a5861932b7fe7f3cbd498c82ff4132763e2fea182cc95e53850394ec1",
|
||||
"sha256:89a6091f2d07936c8a96ce56f2000ecbef20fb420a94845e7d53913c558a6378",
|
||||
"sha256:8af4b3116e4a37059bc8c7fe36d4a73d7c1d8802a1d8b6e549f1380d13a40160",
|
||||
"sha256:8b4b0034e6c7f30133fa64a1cc276f8f1a155ef9529e7eb93a3c1728b40c0f5c",
|
||||
"sha256:92195df3913c1de80062635bf64cd7bd0d0934a7fa1689b6d287d1cbbd16922c",
|
||||
"sha256:96c2e68385f3848d58f19b2975a675532abdb65c8fa5f04d94b95b27b6b1ffa7",
|
||||
"sha256:9c7044dbbf8c58420a9ef4ed6901f5a8b7698d90cd984d7f57a18c78474686f6",
|
||||
"sha256:a1937efed7e3fe0ee74630e1960df887d8aa83c571e1cf4db9d15b9c181d457d",
|
||||
"sha256:a38c10423a475a1658e2cb8f52cf84ec20a4c0adff724dd43a6b45183f499bc1",
|
||||
"sha256:a413c424199bcbab71bf5fa7538246f27177fbd6dd74b2d9c5f34878658807f8",
|
||||
"sha256:b18a855f8504743e0a2d8b75d008c7720d44e4c76687e13f959e35d9a13eb397",
|
||||
"sha256:b4d59ab3608538e550a72cea13d3c209dd72b6e19e832688da7884081c01594e",
|
||||
"sha256:b51d3f1cd87f488455f43046d72003689024b0fa9b2d53635db7523033b19996",
|
||||
"sha256:c02105deda867d09cdd5088d08708f06d75759df6f83d8f7007b06f422908a30",
|
||||
"sha256:c7b6032dc4490b0dcaf078f09f5b382dc35493cb7f473840368bf0de3196c2b6",
|
||||
"sha256:c95b355dba2aaf5177dff943b25ded0529a7feb80021d5fdb114a99f0a1ef508",
|
||||
"sha256:c980ae87863d76b1ea9a073d6d95554b4135032d34bc541be50c07d4a085821b",
|
||||
"sha256:d12895cd083e35e9e032eb4b57645b91116f8979527381a8d864d1f6b8cb4a2e",
|
||||
"sha256:d3cd9bad547a8e5fbe712a1dc1413aff1b917e8d39a2cd1389a6f933b7a21460",
|
||||
"sha256:e8809b01f27f679e3023b9e2013051e0a3f17abff4228cb5197663afd8a0f2c7",
|
||||
"sha256:f3c37b0dc1898e305aad4f7a1d75f6da83036588c28a9ce0afc681ff5245a601",
|
||||
"sha256:f966765f54b536e791541458de84a737a6adba8467190f17a8fe7f85354ba908",
|
||||
"sha256:fa939c2e2468142c9773443d4038e7c915b0cc1b670d3c9192bdc503f7ea73e9",
|
||||
"sha256:fcc5c1f95102989d2e116ffc8467963554ce89f30a65a3ea86a4d06849c498d8"
|
||||
"sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
|
||||
"sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
|
||||
"sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
|
||||
"sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
|
||||
"sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
|
||||
"sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
|
||||
"sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
|
||||
"sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
|
||||
"sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
|
||||
"sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
|
||||
"sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
|
||||
"sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
|
||||
"sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
|
||||
"sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
|
||||
"sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
|
||||
"sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
|
||||
"sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
|
||||
"sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
|
||||
"sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
|
||||
"sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
|
||||
"sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
|
||||
"sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
|
||||
"sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
|
||||
"sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
|
||||
"sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
|
||||
"sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
|
||||
"sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
|
||||
"sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
|
||||
"sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
|
||||
"sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
|
||||
"sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
|
||||
"sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
|
||||
"sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
|
||||
"sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
|
||||
"sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
|
||||
"sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
|
||||
"sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
|
||||
"sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
|
||||
"sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
|
||||
"sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
|
||||
"sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
|
||||
"sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
|
||||
"sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
|
||||
"sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
|
||||
"sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
|
||||
"sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
|
||||
"sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
|
||||
"sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
|
||||
"sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
|
||||
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
|
||||
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
|
||||
],
|
||||
"version": "==5.3.0"
|
||||
"version": "==5.4.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
|
12
README.md
12
README.md
@ -4,13 +4,13 @@
|
||||
|
||||
---
|
||||
|
||||
[](https://discord.gg/KPnmtNWy)
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1)
|
||||
[](https://codecov.io/gh/BeryJu/authentik)
|
||||
[](https://discord.gg/jg33eMhnj6)
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
|
||||
[](https://codecov.io/gh/goauthentik/authentik)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## What is authentik?
|
||||
|
||||
@ -31,7 +31,7 @@ Light | Dark
|
||||
|
||||
## Development
|
||||
|
||||
See [Development Documentation](https://goauthentik.io/docs/development/local-dev-environment)
|
||||
See [Development Documentation](https://goauthentik.io/developer-docs/)
|
||||
|
||||
## Security
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.4.2"
|
||||
__version__ = "2021.4.6"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -4,7 +4,7 @@ from celery.schedules import crontab
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"admin_latest_version": {
|
||||
"task": "authentik.admin.tasks.update_latest_version",
|
||||
"schedule": crontab(minute=0), # Run every hour
|
||||
"schedule": crontab(minute="*/60"), # Run every hour
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ URL_FINDER = URLValidator.regex.pattern[1:]
|
||||
def update_latest_version(self: MonitoredTask):
|
||||
"""Update latest version info"""
|
||||
try:
|
||||
response = get("https://api.github.com/repos/beryju/authentik/releases/latest")
|
||||
response = get(
|
||||
"https://api.github.com/repos/goauthentik/authentik/releases/latest"
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
tag_name = data.get("tag_name")
|
||||
|
@ -4,6 +4,7 @@ from binascii import Error
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from rest_framework.request import Request
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
@ -14,7 +15,7 @@ LOGGER = get_logger()
|
||||
|
||||
# pylint: disable=too-many-return-statements
|
||||
def token_from_header(raw_header: bytes) -> Optional[Token]:
|
||||
"""raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
|
||||
"""raw_header in the Format of `Bearer dGVzdDp0ZXN0`"""
|
||||
auth_credentials = raw_header.decode()
|
||||
if auth_credentials == "":
|
||||
return None
|
||||
@ -25,28 +26,27 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
|
||||
auth_type, body = plain.split()
|
||||
auth_credentials = f"{auth_type} {b64encode(body.encode()).decode()}"
|
||||
except (UnicodeDecodeError, Error):
|
||||
return None
|
||||
raise AuthenticationFailed("Malformed header")
|
||||
auth_type, auth_credentials = auth_credentials.split()
|
||||
if auth_type.lower() not in ["basic", "bearer"]:
|
||||
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
|
||||
return None
|
||||
raise AuthenticationFailed("Unsupported authentication type")
|
||||
password = auth_credentials
|
||||
if auth_type.lower() == "basic":
|
||||
try:
|
||||
auth_credentials = b64decode(auth_credentials.encode()).decode()
|
||||
except (UnicodeDecodeError, Error):
|
||||
return None
|
||||
raise AuthenticationFailed("Malformed header")
|
||||
# Accept credentials with username and without
|
||||
if ":" in auth_credentials:
|
||||
_, password = auth_credentials.split(":")
|
||||
else:
|
||||
password = auth_credentials
|
||||
if password == "": # nosec
|
||||
return None
|
||||
raise AuthenticationFailed("Malformed header")
|
||||
tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
|
||||
if not tokens.exists():
|
||||
LOGGER.debug("Token not found")
|
||||
return None
|
||||
raise AuthenticationFailed("Token invalid/expired")
|
||||
return tokens.first()
|
||||
|
||||
|
||||
@ -58,10 +58,8 @@ class AuthentikTokenAuthentication(BaseAuthentication):
|
||||
auth = get_authorization_header(request)
|
||||
|
||||
token = token_from_header(auth)
|
||||
# None is only returned when the header isn't set.
|
||||
if not token:
|
||||
return None
|
||||
|
||||
return (token.user, None)
|
||||
|
||||
def authenticate_header(self, request: Request) -> str:
|
||||
return "Bearer"
|
||||
|
@ -11,6 +11,29 @@ authentik API Browser
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
let cookieValue = "";
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
const rapidocEl = document.querySelector('rapi-doc');
|
||||
rapidocEl.addEventListener('before-try', (e) => {
|
||||
e.detail.request.headers.append('X-CSRFToken', getCookie("authentik_csrf"));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<rapi-doc
|
||||
spec-url="{{ path }}"
|
||||
heading-text="authentik"
|
||||
|
@ -3,6 +3,7 @@ from base64 import b64encode
|
||||
|
||||
from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
|
||||
from authentik.api.auth import token_from_header
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
@ -28,17 +29,21 @@ class TestAPIAuth(TestCase):
|
||||
|
||||
def test_invalid_type(self):
|
||||
"""Test invalid type"""
|
||||
self.assertIsNone(token_from_header("foo bar".encode()))
|
||||
with self.assertRaises(AuthenticationFailed):
|
||||
token_from_header("foo bar".encode())
|
||||
|
||||
def test_invalid_decode(self):
|
||||
"""Test invalid bas64"""
|
||||
self.assertIsNone(token_from_header("Basic bar".encode()))
|
||||
with self.assertRaises(AuthenticationFailed):
|
||||
token_from_header("Basic bar".encode())
|
||||
|
||||
def test_invalid_empty_password(self):
|
||||
"""Test invalid with empty password"""
|
||||
self.assertIsNone(token_from_header("Basic :".encode()))
|
||||
with self.assertRaises(AuthenticationFailed):
|
||||
token_from_header("Basic :".encode())
|
||||
|
||||
def test_invalid_no_token(self):
|
||||
"""Test invalid with no token"""
|
||||
auth = b64encode(":abc".encode()).decode()
|
||||
self.assertIsNone(token_from_header(f"Basic :{auth}".encode()))
|
||||
with self.assertRaises(AuthenticationFailed):
|
||||
auth = b64encode(":abc".encode()).decode()
|
||||
self.assertIsNone(token_from_header(f"Basic :{auth}".encode()))
|
||||
|
@ -113,6 +113,7 @@ router.register("core/user_consent", UserConsentViewSet)
|
||||
router.register("core/tokens", TokenViewSet)
|
||||
|
||||
router.register("outposts/outposts", OutpostViewSet)
|
||||
router.register("outposts/instances", OutpostViewSet)
|
||||
router.register("outposts/service_connections/all", ServiceConnectionViewSet)
|
||||
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
|
||||
router.register(
|
||||
@ -195,7 +196,8 @@ info = openapi.Info(
|
||||
default_version="v2beta",
|
||||
contact=openapi.Contact(email="hello@beryju.org"),
|
||||
license=openapi.License(
|
||||
name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE"
|
||||
name="GNU GPLv3",
|
||||
url="https://github.com/goauthentik/authentik/blob/master/LICENSE",
|
||||
),
|
||||
)
|
||||
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
|
||||
|
@ -91,6 +91,15 @@ class ApplicationViewSet(ModelViewSet):
|
||||
applications.append(application)
|
||||
return applications
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
name="superuser_full_list",
|
||||
in_=openapi.IN_QUERY,
|
||||
type=openapi.TYPE_BOOLEAN,
|
||||
)
|
||||
]
|
||||
)
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Custom list method that checks Policy based access instead of guardian"""
|
||||
queryset = self._filter_queryset_for_list(self.get_queryset())
|
||||
@ -98,6 +107,13 @@ class ApplicationViewSet(ModelViewSet):
|
||||
|
||||
should_cache = request.GET.get("search", "") == ""
|
||||
|
||||
superuser_full_list = (
|
||||
str(request.GET.get("superuser_full_list", "false")).lower() == "true"
|
||||
)
|
||||
if superuser_full_list and request.user.is_superuser:
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
allowed_applications = []
|
||||
if not should_cache:
|
||||
allowed_applications = self._get_allowed_applications(queryset)
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""PropertyMapping API Views"""
|
||||
from json import dumps
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework import mixins
|
||||
@ -93,6 +94,7 @@ class PropertyMappingViewSet(
|
||||
"description": subclass.__doc__,
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
@ -101,6 +103,13 @@ class PropertyMappingViewSet(
|
||||
@swagger_auto_schema(
|
||||
request_body=PolicyTestSerializer(),
|
||||
responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"},
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
name="format_result",
|
||||
in_=openapi.IN_QUERY,
|
||||
type=openapi.TYPE_BOOLEAN,
|
||||
)
|
||||
],
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
|
||||
# pylint: disable=unused-argument, invalid-name
|
||||
@ -111,6 +120,8 @@ class PropertyMappingViewSet(
|
||||
if not test_params.is_valid():
|
||||
return Response(test_params.errors, status=400)
|
||||
|
||||
format_result = str(request.GET.get("format_result", "false")).lower() == "true"
|
||||
|
||||
# User permission check, only allow mapping testing for users that are readable
|
||||
users = get_objects_for_user(request.user, "authentik_core.view_user").filter(
|
||||
pk=test_params.validated_data["user"].pk
|
||||
@ -125,7 +136,9 @@ class PropertyMappingViewSet(
|
||||
self.request,
|
||||
**test_params.validated_data.get("context", {}),
|
||||
)
|
||||
response_data["result"] = dumps(result)
|
||||
response_data["result"] = dumps(
|
||||
result, indent=(4 if format_result else None)
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
response_data["result"] = str(exc)
|
||||
response_data["successful"] = False
|
||||
|
@ -78,6 +78,7 @@ class ProviderViewSet(
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
data.append(
|
||||
@ -85,6 +86,7 @@ class ProviderViewSet(
|
||||
"name": _("SAML Provider from Metadata"),
|
||||
"description": _("Create a SAML Provider by importing its Metadata."),
|
||||
"component": "ak-provider-saml-import-form",
|
||||
"model_name": "",
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
|
@ -81,6 +81,7 @@ class SourceViewSet(
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
"component": component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
|
@ -11,7 +11,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.models import Token
|
||||
from authentik.core.models import Token, TokenIntents
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.managed.api import ManagedSerializer
|
||||
|
||||
@ -64,7 +64,7 @@ class TokenViewSet(ModelViewSet):
|
||||
ordering = ["expires"]
|
||||
|
||||
def perform_create(self, serializer: TokenSerializer):
|
||||
serializer.save(user=self.request.user)
|
||||
serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
|
||||
|
||||
@permission_required("authentik_core.view_token_key")
|
||||
@swagger_auto_schema(
|
||||
|
@ -48,6 +48,7 @@ class TypeCreateSerializer(PassiveSerializer):
|
||||
name = CharField(required=True)
|
||||
description = CharField(required=True)
|
||||
component = CharField(required=True)
|
||||
model_name = CharField(required=True)
|
||||
|
||||
|
||||
class CacheSerializer(PassiveSerializer):
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Channels base classes"""
|
||||
from channels.exceptions import DenyConnection
|
||||
from channels.generic.websocket import JsonWebsocketConsumer
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.auth import token_from_header
|
||||
@ -22,9 +23,13 @@ class AuthJsonConsumer(JsonWebsocketConsumer):
|
||||
|
||||
raw_header = headers[b"authorization"]
|
||||
|
||||
token = token_from_header(raw_header)
|
||||
if not token:
|
||||
LOGGER.warning("Failed to authenticate")
|
||||
try:
|
||||
token = token_from_header(raw_header)
|
||||
# token is only None when no header was given, in which case we deny too
|
||||
if not token:
|
||||
raise DenyConnection()
|
||||
except AuthenticationFailed as exc:
|
||||
LOGGER.warning("Failed to authenticate", exc=exc)
|
||||
raise DenyConnection()
|
||||
|
||||
self.user = token.user
|
||||
|
@ -15,6 +15,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/empty-state.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/spinner.css' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}">
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
<script src="{% static 'dist/poly.js' %}?v={{ ak_version }}" type="module"></script>
|
||||
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
|
||||
{% block head %}
|
||||
|
@ -12,7 +12,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="fas fa-exclamation-circle pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
|
@ -10,7 +10,7 @@
|
||||
{% block body %}
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-interface-admin>
|
||||
<section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state" style="height: 100vh;">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
|
||||
|
@ -3,6 +3,10 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_before %}
|
||||
<script>ShadyDOM = { force: !navigator.webdriver };</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script>
|
||||
{% endblock %}
|
||||
@ -10,7 +14,7 @@
|
||||
{% block body %}
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-flow-executor>
|
||||
<section class="ak-initial-load pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
|
||||
<div class="pf-c-empty-state" style="height: 100vh;">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
|
||||
|
@ -2,7 +2,7 @@
|
||||
from django.urls.base import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Token, User
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
|
||||
|
||||
class TestTokenAPI(APITestCase):
|
||||
@ -19,4 +19,6 @@ class TestTokenAPI(APITestCase):
|
||||
reverse("authentik_api:token-list"), {"identifier": "test-token"}
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Token.objects.get(identifier="test-token").user, self.user)
|
||||
token = Token.objects.get(identifier="test-token")
|
||||
self.assertEqual(token.user, self.user)
|
||||
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||
|
@ -153,10 +153,6 @@ class EventViewSet(ReadOnlyModelViewSet):
|
||||
data = []
|
||||
for value, name in EventAction.choices:
|
||||
data.append(
|
||||
{
|
||||
"name": name,
|
||||
"description": "",
|
||||
"component": value,
|
||||
}
|
||||
{"name": name, "description": "", "component": value, "model_name": ""}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
|
@ -80,6 +80,7 @@ class StageViewSet(
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
data = sorted(data, key=lambda x: x["name"])
|
||||
|
@ -160,7 +160,7 @@ class FlowImporter:
|
||||
try:
|
||||
model: SerializerModel = apps.get_model(model_app_label, model_name)
|
||||
except LookupError:
|
||||
self.logger.error(
|
||||
self.logger.warning(
|
||||
"app or model does not exist", app=model_app_label, model=model_name
|
||||
)
|
||||
return False
|
||||
@ -168,7 +168,7 @@ class FlowImporter:
|
||||
try:
|
||||
serializer = self._validate_single(entry)
|
||||
except EntryInvalidError as exc:
|
||||
self.logger.error("entry not valid", entry=entry, error=exc)
|
||||
self.logger.warning("entry not valid", entry=entry, error=exc)
|
||||
return False
|
||||
|
||||
model = serializer.save()
|
||||
|
@ -14,6 +14,7 @@ from drf_yasg import openapi
|
||||
from drf_yasg.utils import no_body, swagger_auto_schema
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.views import APIView
|
||||
from sentry_sdk import capture_exception
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
@ -152,7 +153,8 @@ class FlowExecutorView(APIView):
|
||||
stage_response = self.current_stage_view.get(request, *args, **kwargs)
|
||||
return to_stage_response(request, stage_response)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
self._logger.exception(exc)
|
||||
capture_exception(exc)
|
||||
self._logger.warning(exc)
|
||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
||||
|
||||
@swagger_auto_schema(
|
||||
@ -180,7 +182,8 @@ class FlowExecutorView(APIView):
|
||||
stage_response = self.current_stage_view.post(request, *args, **kwargs)
|
||||
return to_stage_response(request, stage_response)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
self._logger.exception(exc)
|
||||
capture_exception(exc)
|
||||
self._logger.warning(exc)
|
||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
||||
|
||||
def _initiate_plan(self) -> FlowPlan:
|
||||
|
@ -5,21 +5,39 @@ from aioredis.errors import ConnectionClosedError, ReplyError
|
||||
from billiard.exceptions import WorkerLostError
|
||||
from botocore.client import ClientError
|
||||
from celery.exceptions import CeleryError
|
||||
from channels.middleware import BaseMiddleware
|
||||
from channels_redis.core import ChannelFull
|
||||
from django.core.exceptions import DisallowedHost, ValidationError
|
||||
from django.core.exceptions import SuspiciousOperation, ValidationError
|
||||
from django.db import InternalError, OperationalError, ProgrammingError
|
||||
from django.http.response import Http404
|
||||
from django_redis.exceptions import ConnectionInterrupted
|
||||
from docker.errors import DockerException
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||
from redis.exceptions import RedisError, ResponseError
|
||||
from rest_framework.exceptions import APIException
|
||||
from sentry_sdk import Hub
|
||||
from sentry_sdk.tracing import Transaction
|
||||
from structlog.stdlib import get_logger
|
||||
from websockets.exceptions import WebSocketException
|
||||
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class SentryWSMiddleware(BaseMiddleware):
|
||||
"""Sentry Websocket middleweare to set the transaction name based on
|
||||
consumer class path"""
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
transaction: Optional[Transaction] = Hub.current.scope.transaction
|
||||
class_path = class_to_path(self.inner.consumer_class)
|
||||
if transaction:
|
||||
transaction.name = class_path
|
||||
return await self.inner(scope, receive, send)
|
||||
|
||||
|
||||
class SentryIgnoredException(Exception):
|
||||
"""Base Class for all errors that are suppressed, and not sent to sentry."""
|
||||
|
||||
@ -36,7 +54,7 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
OperationalError,
|
||||
InternalError,
|
||||
ProgrammingError,
|
||||
DisallowedHost,
|
||||
SuspiciousOperation,
|
||||
ValidationError,
|
||||
# Redis errors
|
||||
RedisConnectionError,
|
||||
@ -61,6 +79,8 @@ def before_send(event: dict, hint: dict) -> Optional[dict]:
|
||||
LDAPException,
|
||||
# Docker errors
|
||||
DockerException,
|
||||
# End-user errors
|
||||
Http404,
|
||||
)
|
||||
if "exc_info" in hint:
|
||||
_, exc_value, _ = hint["exc_info"]
|
||||
|
@ -82,6 +82,7 @@ class ServiceConnectionViewSet(
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
|
@ -1,17 +1,8 @@
|
||||
"""authentik outposts app config"""
|
||||
from importlib import import_module
|
||||
from os import R_OK, access
|
||||
from os.path import expanduser
|
||||
from pathlib import Path
|
||||
from socket import gethostname
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
from django.apps import AppConfig
|
||||
from django.db import ProgrammingError
|
||||
from docker.constants import DEFAULT_UNIX_SOCKET
|
||||
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
|
||||
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -27,49 +18,8 @@ class AuthentikOutpostConfig(AppConfig):
|
||||
def ready(self):
|
||||
import_module("authentik.outposts.signals")
|
||||
try:
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
|
||||
outpost_local_connection.delay()
|
||||
except ProgrammingError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def init_local_connection():
|
||||
"""Check if local kubernetes or docker connections should be created"""
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
KubernetesServiceConnection,
|
||||
)
|
||||
|
||||
# Explicitly check against token filename, as thats
|
||||
# only present when the integration is enabled
|
||||
if Path(SERVICE_TOKEN_FILENAME).exists():
|
||||
LOGGER.debug("Detected in-cluster Kubernetes Config")
|
||||
if not KubernetesServiceConnection.objects.filter(local=True).exists():
|
||||
LOGGER.debug("Created Service Connection for in-cluster")
|
||||
KubernetesServiceConnection.objects.create(
|
||||
name="Local Kubernetes Cluster", local=True, kubeconfig={}
|
||||
)
|
||||
# For development, check for the existence of a kubeconfig file
|
||||
kubeconfig_path = expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
|
||||
if Path(kubeconfig_path).exists():
|
||||
LOGGER.debug("Detected kubeconfig")
|
||||
kubeconfig_local_name = f"k8s-{gethostname()}"
|
||||
if not KubernetesServiceConnection.objects.filter(
|
||||
name=kubeconfig_local_name
|
||||
).exists():
|
||||
LOGGER.debug("Creating kubeconfig Service Connection")
|
||||
with open(kubeconfig_path, "r") as _kubeconfig:
|
||||
KubernetesServiceConnection.objects.create(
|
||||
name=kubeconfig_local_name,
|
||||
kubeconfig=yaml.safe_load(_kubeconfig),
|
||||
)
|
||||
unix_socket_path = urlparse(DEFAULT_UNIX_SOCKET).path
|
||||
socket = Path(unix_socket_path)
|
||||
if socket.exists() and access(socket, R_OK):
|
||||
LOGGER.debug("Detected local docker socket")
|
||||
if len(DockerServiceConnection.objects.filter(local=True)) == 0:
|
||||
LOGGER.debug("Created Service Connection for docker")
|
||||
DockerServiceConnection.objects.create(
|
||||
name="Local Docker connection",
|
||||
local=True,
|
||||
url=unix_socket_path,
|
||||
)
|
||||
|
@ -134,7 +134,8 @@ class DockerController(BaseController):
|
||||
def down(self):
|
||||
try:
|
||||
container, _ = self._get_container()
|
||||
container.kill()
|
||||
if container.status == "running":
|
||||
container.kill()
|
||||
container.remove()
|
||||
except DockerException as exc:
|
||||
raise ControllerException from exc
|
||||
|
@ -201,7 +201,7 @@ class DockerServiceConnection(OutpostServiceConnection):
|
||||
)
|
||||
client.containers.list()
|
||||
except DockerException as exc:
|
||||
LOGGER.error(exc)
|
||||
LOGGER.warning(exc)
|
||||
raise ServiceConnectionInvalid from exc
|
||||
return client
|
||||
|
||||
|
@ -9,7 +9,7 @@ CELERY_BEAT_SCHEDULE = {
|
||||
},
|
||||
"outposts_service_connection_check": {
|
||||
"task": "authentik.outposts.tasks.outpost_service_connection_monitor",
|
||||
"schedule": crontab(minute=0, hour="*"),
|
||||
"schedule": crontab(minute="*/60"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
"outpost_token_ensurer": {
|
||||
@ -17,4 +17,9 @@ CELERY_BEAT_SCHEDULE = {
|
||||
"schedule": crontab(minute="*/5"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
"outpost_local_connection": {
|
||||
"task": "authentik.outposts.tasks.outpost_local_connection",
|
||||
"schedule": crontab(minute="*/60"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
}
|
||||
|
@ -1,11 +1,20 @@
|
||||
"""outpost tasks"""
|
||||
from os import R_OK, access
|
||||
from os.path import expanduser
|
||||
from pathlib import Path
|
||||
from socket import gethostname
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.core.cache import cache
|
||||
from django.db.models.base import Model
|
||||
from django.utils.text import slugify
|
||||
from docker.constants import DEFAULT_UNIX_SOCKET
|
||||
from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
|
||||
from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||
@ -67,6 +76,8 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str):
|
||||
outpost: Outpost = Outpost.objects.get(pk=outpost_pk)
|
||||
self.set_uid(slugify(outpost.name))
|
||||
try:
|
||||
if not outpost.service_connection:
|
||||
return
|
||||
if outpost.type == OutpostType.PROXY:
|
||||
service_connection = outpost.service_connection
|
||||
if isinstance(service_connection, DockerServiceConnection):
|
||||
@ -183,3 +194,42 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
||||
for state in OutpostState.for_outpost(outpost):
|
||||
LOGGER.debug("sending update", channel=state.uid, outpost=outpost)
|
||||
async_to_sync(layer.send)(state.uid, {"type": "event.update"})
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def outpost_local_connection():
|
||||
"""Checks the local environment and create Service connections."""
|
||||
# Explicitly check against token filename, as thats
|
||||
# only present when the integration is enabled
|
||||
if Path(SERVICE_TOKEN_FILENAME).exists():
|
||||
LOGGER.debug("Detected in-cluster Kubernetes Config")
|
||||
if not KubernetesServiceConnection.objects.filter(local=True).exists():
|
||||
LOGGER.debug("Created Service Connection for in-cluster")
|
||||
KubernetesServiceConnection.objects.create(
|
||||
name="Local Kubernetes Cluster", local=True, kubeconfig={}
|
||||
)
|
||||
# For development, check for the existence of a kubeconfig file
|
||||
kubeconfig_path = expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
|
||||
if Path(kubeconfig_path).exists():
|
||||
LOGGER.debug("Detected kubeconfig")
|
||||
kubeconfig_local_name = f"k8s-{gethostname()}"
|
||||
if not KubernetesServiceConnection.objects.filter(
|
||||
name=kubeconfig_local_name
|
||||
).exists():
|
||||
LOGGER.debug("Creating kubeconfig Service Connection")
|
||||
with open(kubeconfig_path, "r") as _kubeconfig:
|
||||
KubernetesServiceConnection.objects.create(
|
||||
name=kubeconfig_local_name,
|
||||
kubeconfig=yaml.safe_load(_kubeconfig),
|
||||
)
|
||||
unix_socket_path = urlparse(DEFAULT_UNIX_SOCKET).path
|
||||
socket = Path(unix_socket_path)
|
||||
if socket.exists() and access(socket, R_OK):
|
||||
LOGGER.debug("Detected local docker socket")
|
||||
if len(DockerServiceConnection.objects.filter(local=True)) == 0:
|
||||
LOGGER.debug("Created Service Connection for docker")
|
||||
DockerServiceConnection.objects.create(
|
||||
name="Local Docker connection",
|
||||
local=True,
|
||||
url=unix_socket_path,
|
||||
)
|
||||
|
@ -108,6 +108,7 @@ class PolicyViewSet(
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(data, many=True).data)
|
||||
|
@ -1,13 +1,23 @@
|
||||
"""Test authorize view"""
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.flows.challenge import ChallengeTypes
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.providers.oauth2.errors import (
|
||||
AuthorizeError,
|
||||
ClientIdError,
|
||||
RedirectUriError,
|
||||
)
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.providers.oauth2.generators import generate_client_id
|
||||
from authentik.providers.oauth2.models import (
|
||||
AuthorizationCode,
|
||||
GrantTypes,
|
||||
OAuth2Provider,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.views.authorize import OAuthAuthorizationParams
|
||||
|
||||
|
||||
@ -32,15 +42,194 @@ class TestViewsAuthorize(TestCase):
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_missing_redirect_uri(self):
|
||||
"""test missing redirect URI"""
|
||||
def test_request(self):
|
||||
"""test request param"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
with self.assertRaises(AuthorizeError):
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"request": "foo",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_redirect_uri(self):
|
||||
"""test missing/invalid redirect URI"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get(
|
||||
"/", data={"response_type": "code", "client_id": "test"}
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
with self.assertRaises(RedirectUriError):
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://localhost",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_response_type(self):
|
||||
"""test response_type"""
|
||||
OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).grant_type,
|
||||
GrantTypes.AUTHORIZATION_CODE,
|
||||
)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "id_token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"scope": "openid",
|
||||
"state": "foo",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).grant_type,
|
||||
GrantTypes.IMPLICIT,
|
||||
)
|
||||
# Implicit without openid scope
|
||||
with self.assertRaises(AuthorizeError):
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "id_token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"state": "foo",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).grant_type,
|
||||
GrantTypes.IMPLICIT,
|
||||
)
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "code token",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
"scope": "openid",
|
||||
"state": "foo",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
OAuthAuthorizationParams.from_request(request).grant_type, GrantTypes.HYBRID
|
||||
)
|
||||
with self.assertRaises(AuthorizeError):
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
data={
|
||||
"response_type": "invalid",
|
||||
"client_id": "test",
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
)
|
||||
OAuthAuthorizationParams.from_request(request)
|
||||
|
||||
def test_full_code(self):
|
||||
"""Test full authorization"""
|
||||
flow = Flow.objects.create(slug="empty")
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=flow,
|
||||
redirect_uris="foo://localhost",
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_client_id()
|
||||
user = User.objects.get(username="akadmin")
|
||||
self.client.force_login(user)
|
||||
# Step 1, initiate params and get redirect to flow
|
||||
self.client.get(
|
||||
reverse("authentik_providers_oauth2:authorize"),
|
||||
data={
|
||||
"response_type": "code",
|
||||
"client_id": "test",
|
||||
"state": state,
|
||||
"redirect_uri": "foo://localhost",
|
||||
},
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
)
|
||||
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"type": ChallengeTypes.REDIRECT.value,
|
||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||
},
|
||||
)
|
||||
|
||||
def test_full_implicit(self):
|
||||
"""Test full authorization"""
|
||||
flow = Flow.objects.create(slug="empty")
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id="test",
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_client_id()
|
||||
user = User.objects.get(username="akadmin")
|
||||
self.client.force_login(user)
|
||||
# Step 1, initiate params and get redirect to flow
|
||||
self.client.get(
|
||||
reverse("authentik_providers_oauth2:authorize"),
|
||||
data={
|
||||
"response_type": "id_token",
|
||||
"client_id": "test",
|
||||
"state": state,
|
||||
"scope": "openid",
|
||||
"redirect_uri": "http://localhost",
|
||||
},
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
)
|
||||
token: RefreshToken = RefreshToken.objects.filter(user=user).first()
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"type": ChallengeTypes.REDIRECT.value,
|
||||
"to": (
|
||||
f"http://localhost#access_token={token.access_token}"
|
||||
f"&id_token={provider.encode(token.id_token.to_dict())}&token_type=bearer"
|
||||
f"&expires_in=600&state={state}"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
222
authentik/providers/oauth2/tests/test_views_token.py
Normal file
222
authentik/providers/oauth2/tests/test_views_token.py
Normal file
@ -0,0 +1,222 @@
|
||||
"""Test token view"""
|
||||
from base64 import b64encode
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.providers.oauth2.constants import (
|
||||
GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
GRANT_TYPE_REFRESH_TOKEN,
|
||||
)
|
||||
from authentik.providers.oauth2.generators import (
|
||||
generate_client_id,
|
||||
generate_client_secret,
|
||||
)
|
||||
from authentik.providers.oauth2.models import (
|
||||
AuthorizationCode,
|
||||
OAuth2Provider,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.views.token import TokenParams
|
||||
|
||||
|
||||
class TestViewsToken(TestCase):
|
||||
"""Test token view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_request_auth_code(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_client_id(),
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
header = b64encode(
|
||||
f"{provider.client_id}:{provider.client_secret}".encode()
|
||||
).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
code = AuthorizationCode.objects.create(
|
||||
code="foobar", provider=provider, user=user
|
||||
)
|
||||
request = self.factory.post(
|
||||
"/",
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
"code": code.code,
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
)
|
||||
params = TokenParams.from_request(request)
|
||||
self.assertEqual(params.provider, provider)
|
||||
|
||||
def test_request_refresh_token(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_client_id(),
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
header = b64encode(
|
||||
f"{provider.client_id}:{provider.client_secret}".encode()
|
||||
).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
refresh_token=generate_client_id(),
|
||||
)
|
||||
request = self.factory.post(
|
||||
"/",
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
|
||||
"refresh_token": token.refresh_token,
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
)
|
||||
params = TokenParams.from_request(request)
|
||||
self.assertEqual(params.provider, provider)
|
||||
|
||||
def test_auth_code_view(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_client_id(),
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
header = b64encode(
|
||||
f"{provider.client_id}:{provider.client_secret}".encode()
|
||||
).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
code = AuthorizationCode.objects.create(
|
||||
code="foobar", provider=provider, user=user
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
"code": code.code,
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
)
|
||||
new_token: RefreshToken = RefreshToken.objects.filter(user=user).first()
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"access_token": new_token.access_token,
|
||||
"refresh_token": new_token.refresh_token,
|
||||
"token_type": "bearer",
|
||||
"expires_in": 600,
|
||||
"id_token": provider.encode(
|
||||
new_token.id_token.to_dict(),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def test_refresh_token_view(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_client_id(),
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
header = b64encode(
|
||||
f"{provider.client_id}:{provider.client_secret}".encode()
|
||||
).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
refresh_token=generate_client_id(),
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
|
||||
"refresh_token": token.refresh_token,
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
HTTP_ORIGIN="http://local.invalid",
|
||||
)
|
||||
new_token: RefreshToken = (
|
||||
RefreshToken.objects.filter(user=user).exclude(pk=token.pk).first()
|
||||
)
|
||||
self.assertEqual(response["Access-Control-Allow-Credentials"], "true")
|
||||
self.assertEqual(
|
||||
response["Access-Control-Allow-Origin"], "http://local.invalid"
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"access_token": new_token.access_token,
|
||||
"refresh_token": new_token.refresh_token,
|
||||
"token_type": "bearer",
|
||||
"expires_in": 600,
|
||||
"id_token": provider.encode(
|
||||
new_token.id_token.to_dict(),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
def test_refresh_token_view_invalid_origin(self):
|
||||
"""test request param"""
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_client_id(),
|
||||
client_secret=generate_client_secret(),
|
||||
authorization_flow=Flow.objects.first(),
|
||||
redirect_uris="http://local.invalid",
|
||||
)
|
||||
header = b64encode(
|
||||
f"{provider.client_id}:{provider.client_secret}".encode()
|
||||
).decode()
|
||||
user = User.objects.get(username="akadmin")
|
||||
token: RefreshToken = RefreshToken.objects.create(
|
||||
provider=provider,
|
||||
user=user,
|
||||
refresh_token=generate_client_id(),
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
data={
|
||||
"grant_type": GRANT_TYPE_REFRESH_TOKEN,
|
||||
"refresh_token": token.refresh_token,
|
||||
"redirect_uri": "http://local.invalid",
|
||||
},
|
||||
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||
HTTP_ORIGIN="http://another.invalid",
|
||||
)
|
||||
new_token: RefreshToken = (
|
||||
RefreshToken.objects.filter(user=user).exclude(pk=token.pk).first()
|
||||
)
|
||||
self.assertNotIn("Access-Control-Allow-Credentials", response)
|
||||
self.assertNotIn("Access-Control-Allow-Origin", response)
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"access_token": new_token.access_token,
|
||||
"refresh_token": new_token.refresh_token,
|
||||
"token_type": "bearer",
|
||||
"expires_in": 600,
|
||||
"id_token": provider.encode(
|
||||
new_token.id_token.to_dict(),
|
||||
),
|
||||
},
|
||||
)
|
@ -2,9 +2,11 @@
|
||||
import re
|
||||
from base64 import b64decode
|
||||
from binascii import Error
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
@ -25,15 +27,34 @@ class TokenResponse(JsonResponse):
|
||||
self["Pragma"] = "no-cache"
|
||||
|
||||
|
||||
def cors_allow_any(request, response):
|
||||
"""
|
||||
Add headers to permit CORS requests from any origin, with or without credentials,
|
||||
with any headers.
|
||||
"""
|
||||
def cors_allow(request: HttpRequest, response: HttpResponse, *allowed_origins: str):
|
||||
"""Add headers to permit CORS requests from allowed_origins, with or without credentials,
|
||||
with any headers."""
|
||||
origin = request.META.get("HTTP_ORIGIN")
|
||||
if not origin:
|
||||
return response
|
||||
|
||||
# OPTIONS requests don't have an authorization header -> hence
|
||||
# we can't extract the provider this request is for
|
||||
# so for options requests we allow the calling origin without checking
|
||||
allowed = request.method == "OPTIONS"
|
||||
received_origin = urlparse(origin)
|
||||
for allowed_origin in allowed_origins:
|
||||
url = urlparse(allowed_origin)
|
||||
if (
|
||||
received_origin.scheme == url.scheme
|
||||
and received_origin.hostname == url.hostname
|
||||
and received_origin.port == url.port
|
||||
):
|
||||
allowed = True
|
||||
if not allowed:
|
||||
LOGGER.warning(
|
||||
"CORS: Origin is not an allowed origin",
|
||||
requested=origin,
|
||||
allowed=allowed_origins,
|
||||
)
|
||||
return response
|
||||
|
||||
# From the CORS spec: The string "*" cannot be used for a resource that supports credentials.
|
||||
response["Access-Control-Allow-Origin"] = origin
|
||||
patch_vary_headers(response, ["Origin"])
|
||||
@ -141,3 +162,18 @@ def protected_resource_view(scopes: list[str]):
|
||||
return view_wrapper
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
"""HTTP Response to redirect, can be to a non-http scheme"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
redirect_to: str,
|
||||
*args: Any,
|
||||
allowed_schemes: Optional[list[str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
super().__init__(redirect_to, *args, **kwargs)
|
||||
|
@ -2,12 +2,12 @@
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
||||
from urllib.parse import parse_qs, urlencode, urlparse, urlsplit, urlunsplit
|
||||
from uuid import uuid4
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.http.response import Http404, HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog.stdlib import get_logger
|
||||
@ -46,6 +46,7 @@ from authentik.providers.oauth2.models import (
|
||||
OAuth2Provider,
|
||||
ResponseTypes,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
|
||||
from authentik.providers.oauth2.views.userinfo import UserInfoView
|
||||
from authentik.stages.consent.models import ConsentMode, ConsentStage
|
||||
from authentik.stages.consent.stage import (
|
||||
@ -233,9 +234,17 @@ class OAuthFulfillmentStage(StageView):
|
||||
params: OAuthAuthorizationParams
|
||||
provider: OAuth2Provider
|
||||
|
||||
def redirect(self, uri: str) -> HttpResponse:
|
||||
"""Redirect using HttpResponseRedirectScheme, compatible with non-http schemes"""
|
||||
parsed = urlparse(uri)
|
||||
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""final Stage of an OAuth2 Flow"""
|
||||
if PLAN_CONTEXT_PARAMS not in self.executor.plan.context:
|
||||
LOGGER.warning("Got to fulfillment stage with no pending context")
|
||||
return HttpResponseBadRequest()
|
||||
self.params: OAuthAuthorizationParams = self.executor.plan.context.pop(
|
||||
PLAN_CONTEXT_PARAMS
|
||||
)
|
||||
@ -258,7 +267,7 @@ class OAuthFulfillmentStage(StageView):
|
||||
flow=self.executor.plan.flow_pk,
|
||||
scopes=", ".join(self.params.scope),
|
||||
).from_http(self.request)
|
||||
return redirect(self.create_response_uri())
|
||||
return self.redirect(self.create_response_uri())
|
||||
except (ClientIdError, RedirectUriError) as error:
|
||||
error.to_event(application=application).from_http(request)
|
||||
self.executor.stage_invalid()
|
||||
@ -267,7 +276,7 @@ class OAuthFulfillmentStage(StageView):
|
||||
except AuthorizeError as error:
|
||||
error.to_event(application=application).from_http(request)
|
||||
self.executor.stage_invalid()
|
||||
return redirect(error.create_uri())
|
||||
return self.redirect(error.create_uri())
|
||||
|
||||
def create_response_uri(self) -> str:
|
||||
"""Create a final Response URI the user is redirected to."""
|
||||
@ -301,7 +310,7 @@ class OAuthFulfillmentStage(StageView):
|
||||
return urlunsplit(uri)
|
||||
raise OAuth2Error()
|
||||
except OAuth2Error as error:
|
||||
LOGGER.exception("Error when trying to create response uri", error=error)
|
||||
LOGGER.warning("Error when trying to create response uri", error=error)
|
||||
raise AuthorizeError(
|
||||
self.params.redirect_uri,
|
||||
"server_error",
|
||||
|
@ -19,7 +19,7 @@ from authentik.providers.oauth2.models import (
|
||||
ResponseTypes,
|
||||
ScopeMapping,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import cors_allow_any
|
||||
from authentik.providers.oauth2.utils import cors_allow
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -30,6 +30,8 @@ PLAN_CONTEXT_SCOPES = "scopes"
|
||||
class ProviderInfoView(View):
|
||||
"""OpenID-compliant Provider Info"""
|
||||
|
||||
provider: OAuth2Provider
|
||||
|
||||
def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
|
||||
"""Get dictionary for OpenID Connect information"""
|
||||
scopes = list(
|
||||
@ -95,19 +97,20 @@ class ProviderInfoView(View):
|
||||
}
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(
|
||||
self, request: HttpRequest, application_slug: str, *args, **kwargs
|
||||
) -> HttpResponse:
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""OpenID-compliant Provider Info"""
|
||||
return JsonResponse(
|
||||
self.get_info(self.provider), json_dumps_params={"indent": 2}
|
||||
)
|
||||
|
||||
def dispatch(
|
||||
self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
|
||||
) -> HttpResponse:
|
||||
# Since this view only supports get, we can statically set the CORS headers
|
||||
application = get_object_or_404(Application, slug=application_slug)
|
||||
provider: OAuth2Provider = get_object_or_404(
|
||||
self.provider: OAuth2Provider = get_object_or_404(
|
||||
OAuth2Provider, pk=application.provider_id
|
||||
)
|
||||
return JsonResponse(self.get_info(provider), json_dumps_params={"indent": 2})
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
# Since this view only supports get, we can statically set the CORS headers
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
cors_allow_any(request, response)
|
||||
cors_allow(request, response, *self.provider.redirect_uris.split("\n"))
|
||||
return response
|
||||
|
@ -19,7 +19,11 @@ from authentik.providers.oauth2.models import (
|
||||
OAuth2Provider,
|
||||
RefreshToken,
|
||||
)
|
||||
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
|
||||
from authentik.providers.oauth2.utils import (
|
||||
TokenResponse,
|
||||
cors_allow,
|
||||
extract_client_auth,
|
||||
)
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -154,7 +158,18 @@ class TokenParams:
|
||||
class TokenView(View):
|
||||
"""Generate tokens for clients"""
|
||||
|
||||
params: TokenParams
|
||||
params: Optional[TokenParams] = None
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
allowed_origins = []
|
||||
if self.params:
|
||||
allowed_origins = self.params.provider.redirect_uris.split("\n")
|
||||
cors_allow(self.request, response, *allowed_origins)
|
||||
return response
|
||||
|
||||
def options(self, request: HttpRequest) -> HttpResponse:
|
||||
return TokenResponse({})
|
||||
|
||||
def post(self, request: HttpRequest) -> HttpResponse:
|
||||
"""Generate tokens for clients"""
|
||||
@ -198,7 +213,7 @@ class TokenView(View):
|
||||
response_dict = {
|
||||
"access_token": refresh_token.access_token,
|
||||
"refresh_token": refresh_token.refresh_token,
|
||||
"token_type": "Bearer",
|
||||
"token_type": "bearer",
|
||||
"expires_in": timedelta_from_string(
|
||||
self.params.provider.token_validity
|
||||
).seconds,
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""authentik OAuth2 OpenID Userinfo views"""
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import HttpResponseBadRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from structlog.stdlib import get_logger
|
||||
@ -13,7 +14,7 @@ from authentik.providers.oauth2.constants import (
|
||||
SCOPE_GITHUB_USER_READ,
|
||||
)
|
||||
from authentik.providers.oauth2.models import RefreshToken, ScopeMapping
|
||||
from authentik.providers.oauth2.utils import TokenResponse, cors_allow_any
|
||||
from authentik.providers.oauth2.utils import TokenResponse, cors_allow
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -22,6 +23,8 @@ class UserInfoView(View):
|
||||
"""Create a dictionary with all the requested claims about the End-User.
|
||||
See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse"""
|
||||
|
||||
token: Optional[RefreshToken]
|
||||
|
||||
def get_scope_descriptions(self, scopes: list[str]) -> list[dict[str, str]]:
|
||||
"""Get a list of all Scopes's descriptions"""
|
||||
scope_descriptions = []
|
||||
@ -79,16 +82,25 @@ class UserInfoView(View):
|
||||
final_claims.update(value)
|
||||
return final_claims
|
||||
|
||||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||
self.token = kwargs.get("token", None)
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
allowed_origins = []
|
||||
if self.token:
|
||||
allowed_origins = self.token.provider.redirect_uris.split("\n")
|
||||
cors_allow(self.request, response, *allowed_origins)
|
||||
return response
|
||||
|
||||
def options(self, request: HttpRequest) -> HttpResponse:
|
||||
return cors_allow_any(self.request, TokenResponse({}))
|
||||
return TokenResponse({})
|
||||
|
||||
def get(self, request: HttpRequest, **kwargs) -> HttpResponse:
|
||||
"""Handle GET Requests for UserInfo"""
|
||||
token: RefreshToken = kwargs["token"]
|
||||
claims = self.get_claims(token)
|
||||
claims["sub"] = token.id_token.sub
|
||||
if not self.token:
|
||||
return HttpResponseBadRequest()
|
||||
claims = self.get_claims(self.token)
|
||||
claims["sub"] = self.token.id_token.sub
|
||||
response = TokenResponse(claims)
|
||||
cors_allow_any(self.request, response)
|
||||
return response
|
||||
|
||||
def post(self, request: HttpRequest, **kwargs) -> HttpResponse:
|
||||
|
@ -21,7 +21,7 @@ class ProxyScopeMappingManager(ObjectManager):
|
||||
EnsureExists(
|
||||
ScopeMapping,
|
||||
"goauthentik.io/providers/proxy/scope-proxy",
|
||||
name="authentik default OAuth Mapping: proxy outpost",
|
||||
name="authentik default OAuth Mapping: Proxy outpost",
|
||||
scope_name=SCOPE_AK_PROXY,
|
||||
expression=SCOPE_AK_PROXY_EXPRESSION,
|
||||
),
|
||||
|
@ -337,7 +337,7 @@ if CONFIG.y("postgresql.s3_backup"):
|
||||
|
||||
# Sentry integration
|
||||
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
|
||||
if not DEBUG and _ERROR_REPORTING:
|
||||
if _ERROR_REPORTING:
|
||||
# pylint: disable=abstract-class-instantiated
|
||||
sentry_init(
|
||||
dsn="https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
|
||||
|
@ -2,10 +2,13 @@
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from django.urls import path
|
||||
|
||||
from authentik.lib.sentry import SentryWSMiddleware
|
||||
from authentik.outposts.channels import OutpostConsumer
|
||||
from authentik.root.messages.consumer import MessageConsumer
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path("ws/outpost/<uuid:pk>/", OutpostConsumer.as_asgi()),
|
||||
path("ws/client/", AuthMiddlewareStack(MessageConsumer.as_asgi())),
|
||||
path("ws/outpost/<uuid:pk>/", SentryWSMiddleware(OutpostConsumer.as_asgi())),
|
||||
path(
|
||||
"ws/client/", AuthMiddlewareStack(SentryWSMiddleware(MessageConsumer.as_asgi()))
|
||||
),
|
||||
]
|
||||
|
@ -8,7 +8,7 @@ AUTHENTICATION_BACKENDS = [
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"sources_ldap_sync": {
|
||||
"task": "authentik.sources.ldap.tasks.ldap_sync_all",
|
||||
"schedule": crontab(minute=0), # Run every hour
|
||||
"schedule": crontab(minute="*/60"), # Run every hour
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ from requests.models import Response
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -39,8 +40,11 @@ class BaseOAuthClient:
|
||||
|
||||
def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
|
||||
"Fetch user profile information."
|
||||
profile_url = self.source.type.profile_url or ""
|
||||
if self.source.type.urls_customizable and self.source.profile_url:
|
||||
profile_url = self.source.profile_url
|
||||
try:
|
||||
response = self.do_request("get", self.source.profile_url, token=token)
|
||||
response = self.do_request("get", profile_url, token=token)
|
||||
response.raise_for_status()
|
||||
except RequestException as exc:
|
||||
LOGGER.warning("Unable to fetch user profile", exc=exc)
|
||||
@ -59,7 +63,16 @@ class BaseOAuthClient:
|
||||
args.update(additional)
|
||||
params = urlencode(args)
|
||||
LOGGER.info("redirect args", **args)
|
||||
return f"{self.source.authorization_url}?{params}"
|
||||
authorization_url = self.source.type.authorization_url or ""
|
||||
if self.source.type.urls_customizable and self.source.authorization_url:
|
||||
authorization_url = self.source.authorization_url
|
||||
if authorization_url == "":
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
source=self.source,
|
||||
message="Source has an empty authorization URL.",
|
||||
).save()
|
||||
return f"{authorization_url}?{params}"
|
||||
|
||||
def parse_raw_token(self, raw_token: str) -> dict[str, Any]:
|
||||
"Parse token and secret from raw token response."
|
||||
|
@ -28,9 +28,12 @@ class OAuthClient(BaseOAuthClient):
|
||||
if raw_token is not None and verifier is not None:
|
||||
token = self.parse_raw_token(raw_token)
|
||||
try:
|
||||
access_token_url = self.source.type.access_token_url or ""
|
||||
if self.source.type.urls_customizable and self.source.access_token_url:
|
||||
access_token_url = self.source.access_token_url
|
||||
response = self.do_request(
|
||||
"post",
|
||||
self.source.access_token_url,
|
||||
access_token_url,
|
||||
token=token,
|
||||
headers=self._default_headers,
|
||||
oauth_verifier=verifier,
|
||||
@ -48,9 +51,12 @@ class OAuthClient(BaseOAuthClient):
|
||||
"Fetch the OAuth request token. Only required for OAuth 1.0."
|
||||
callback = self.request.build_absolute_uri(self.callback)
|
||||
try:
|
||||
request_token_url = self.source.type.request_token_url or ""
|
||||
if self.source.type.urls_customizable and self.source.request_token_url:
|
||||
request_token_url = self.source.request_token_url
|
||||
response = self.do_request(
|
||||
"post",
|
||||
self.source.request_token_url,
|
||||
request_token_url,
|
||||
headers=self._default_headers,
|
||||
oauth_callback=callback,
|
||||
)
|
||||
|
@ -56,9 +56,12 @@ class OAuth2Client(BaseOAuthClient):
|
||||
LOGGER.warning("No code returned by the source")
|
||||
return None
|
||||
try:
|
||||
access_token_url = self.source.type.access_token_url or ""
|
||||
if self.source.type.urls_customizable and self.source.access_token_url:
|
||||
access_token_url = self.source.access_token_url
|
||||
response = self.session.request(
|
||||
"post",
|
||||
self.source.access_token_url,
|
||||
access_token_url,
|
||||
data=args,
|
||||
headers=self._default_headers,
|
||||
)
|
||||
|
@ -0,0 +1,79 @@
|
||||
# Generated by Django 3.2 on 2021-04-17 19:00
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def update_empty_urls(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
OAuthSource = apps.get_model("authentik_sources_oauth", "oauthsource")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
for source in OAuthSource.objects.using(db_alias).all():
|
||||
changed = False
|
||||
if source.access_token_url == "":
|
||||
source.access_token_url = None
|
||||
changed = True
|
||||
if source.authorization_url == "":
|
||||
source.authorization_url = None
|
||||
changed = True
|
||||
if source.profile_url == "":
|
||||
source.profile_url = None
|
||||
changed = True
|
||||
if source.request_token_url == "":
|
||||
source.request_token_url = None
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
source.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_oauth", "0003_auto_20210416_0726"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="oauthsource",
|
||||
name="access_token_url",
|
||||
field=models.CharField(
|
||||
help_text="URL used by authentik to retrive tokens.",
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="Access Token URL",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauthsource",
|
||||
name="authorization_url",
|
||||
field=models.CharField(
|
||||
help_text="URL the user is redirect to to conest the flow.",
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="Authorization URL",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauthsource",
|
||||
name="profile_url",
|
||||
field=models.CharField(
|
||||
help_text="URL used by authentik to get user information.",
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="Profile URL",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauthsource",
|
||||
name="request_token_url",
|
||||
field=models.CharField(
|
||||
help_text="URL used to request the initial token. This URL is only required for OAuth 1.",
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="Request Token URL",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(update_empty_urls),
|
||||
]
|
@ -19,7 +19,7 @@ class OAuthSource(Source):
|
||||
|
||||
provider_type = models.CharField(max_length=255)
|
||||
request_token_url = models.CharField(
|
||||
blank=True,
|
||||
null=True,
|
||||
max_length=255,
|
||||
verbose_name=_("Request Token URL"),
|
||||
help_text=_(
|
||||
@ -28,19 +28,19 @@ class OAuthSource(Source):
|
||||
)
|
||||
authorization_url = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Authorization URL"),
|
||||
help_text=_("URL the user is redirect to to conest the flow."),
|
||||
)
|
||||
access_token_url = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Access Token URL"),
|
||||
help_text=_("URL used by authentik to retrive tokens."),
|
||||
)
|
||||
profile_url = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Profile URL"),
|
||||
help_text=_("URL used by authentik to get user information."),
|
||||
)
|
||||
@ -163,6 +163,16 @@ class OpenIDOAuthSource(OAuthSource):
|
||||
verbose_name_plural = _("OpenID OAuth Sources")
|
||||
|
||||
|
||||
class PlexOAuthSource(OAuthSource):
|
||||
"""Login using plex.tv."""
|
||||
|
||||
class Meta:
|
||||
|
||||
abstract = True
|
||||
verbose_name = _("Plex OAuth Source")
|
||||
verbose_name_plural = _("Plex OAuth Sources")
|
||||
|
||||
|
||||
class UserOAuthSourceConnection(UserSourceConnection):
|
||||
"""Authorized remote OAuth provider."""
|
||||
|
||||
|
@ -9,4 +9,5 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
"authentik.sources.oauth.types.azure_ad",
|
||||
"authentik.sources.oauth.types.oidc",
|
||||
"authentik.sources.oauth.types.plex",
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""AzureAD OAuth2 Views"""
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
@ -10,8 +10,11 @@ from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
class AzureADOAuthCallback(OAuthCallback):
|
||||
"""AzureAD OAuth2 Callback"""
|
||||
|
||||
def get_user_id(self, source: OAuthSource, info: dict[str, Any]) -> str:
|
||||
return str(UUID(info.get("objectId")).int)
|
||||
def get_user_id(self, source: OAuthSource, info: dict[str, Any]) -> Optional[str]:
|
||||
try:
|
||||
return str(UUID(info.get("objectId")).int)
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
|
134
authentik/sources/oauth/types/plex.py
Normal file
134
authentik/sources/oauth/types/plex.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""Plex OAuth Views"""
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.http.response import Http404
|
||||
from requests import post
|
||||
from requests.api import get
|
||||
from requests.exceptions import RequestException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
||||
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
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
|
||||
|
||||
LOGGER = get_logger()
|
||||
SESSION_ID_KEY = "PLEX_ID"
|
||||
SESSION_CODE_KEY = "PLEX_CODE"
|
||||
DEFAULT_PAYLOAD = {
|
||||
"X-Plex-Product": "authentik",
|
||||
"X-Plex-Version": __version__,
|
||||
"X-Plex-Device-Vendor": "BeryJu.org",
|
||||
}
|
||||
|
||||
|
||||
class PlexRedirect(OAuthRedirect):
|
||||
"""Plex Auth redirect, get a pin then redirect to a URL to claim it"""
|
||||
|
||||
headers = {}
|
||||
|
||||
def get_pin(self, **data) -> dict:
|
||||
"""Get plex pin that the user will claim
|
||||
https://forums.plex.tv/t/authenticating-with-plex/609370"""
|
||||
return post(
|
||||
"https://plex.tv/api/v2/pins.json?strong=true",
|
||||
data=data,
|
||||
headers=self.headers,
|
||||
).json()
|
||||
|
||||
def get_redirect_url(self, **kwargs) -> str:
|
||||
slug = kwargs.get("source_slug", "")
|
||||
self.headers = {"Origin": self.request.build_absolute_uri("/")}
|
||||
try:
|
||||
source: OAuthSource = OAuthSource.objects.get(slug=slug)
|
||||
except OAuthSource.DoesNotExist:
|
||||
raise Http404(f"Unknown OAuth source '{slug}'.")
|
||||
else:
|
||||
payload = DEFAULT_PAYLOAD.copy()
|
||||
payload["X-Plex-Client-Identifier"] = source.consumer_key
|
||||
# Get a pin first
|
||||
pin = self.get_pin(**payload)
|
||||
LOGGER.debug("Got pin", **pin)
|
||||
self.request.session[SESSION_ID_KEY] = pin["id"]
|
||||
self.request.session[SESSION_CODE_KEY] = pin["code"]
|
||||
qs = {
|
||||
"clientID": source.consumer_key,
|
||||
"code": pin["code"],
|
||||
"forwardUrl": self.request.build_absolute_uri(
|
||||
self.get_callback_url(source)
|
||||
),
|
||||
}
|
||||
return f"https://app.plex.tv/auth#!?{urlencode(qs)}"
|
||||
|
||||
|
||||
class PlexOAuthClient(OAuth2Client):
|
||||
"""Retrive the plex token after authentication, then ask the plex API about user info"""
|
||||
|
||||
def check_application_state(self) -> bool:
|
||||
return SESSION_ID_KEY in self.request.session
|
||||
|
||||
def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]:
|
||||
payload = dict(DEFAULT_PAYLOAD)
|
||||
payload["X-Plex-Client-Identifier"] = self.source.consumer_key
|
||||
payload["Accept"] = "application/json"
|
||||
response = get(
|
||||
f"https://plex.tv/api/v2/pins/{self.request.session[SESSION_ID_KEY]}",
|
||||
headers=payload,
|
||||
)
|
||||
response.raise_for_status()
|
||||
token = response.json()["authToken"]
|
||||
return {"plex_token": token}
|
||||
|
||||
def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
|
||||
"Fetch user profile information."
|
||||
qs = {"X-Plex-Token": token["plex_token"]}
|
||||
try:
|
||||
response = self.do_request(
|
||||
"get", f"https://plex.tv/users/account.json?{urlencode(qs)}"
|
||||
)
|
||||
response.raise_for_status()
|
||||
except RequestException as exc:
|
||||
LOGGER.warning("Unable to fetch user profile", exc=exc)
|
||||
return None
|
||||
else:
|
||||
return response.json().get("user", {})
|
||||
|
||||
|
||||
class PlexOAuth2Callback(OAuthCallback):
|
||||
"""Plex OAuth2 Callback"""
|
||||
|
||||
client_class = PlexOAuthClient
|
||||
|
||||
def get_user_id(
|
||||
self, source: UserOAuthSourceConnection, info: dict[str, Any]
|
||||
) -> Optional[str]:
|
||||
return info.get("uuid")
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
source: OAuthSource,
|
||||
access: UserOAuthSourceConnection,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"username": info.get("username"),
|
||||
"email": info.get("email"),
|
||||
"name": info.get("title"),
|
||||
}
|
||||
|
||||
|
||||
@MANAGER.type()
|
||||
class PlexType(SourceType):
|
||||
"""Plex Type definition"""
|
||||
|
||||
redirect_view = PlexRedirect
|
||||
callback_view = PlexOAuth2Callback
|
||||
name = "Plex"
|
||||
slug = "plex"
|
||||
|
||||
authorization_url = ""
|
||||
access_token_url = "" # nosec
|
||||
profile_url = ""
|
@ -2,12 +2,15 @@
|
||||
from typing import Optional, Type
|
||||
|
||||
from django.http.request import HttpRequest
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.sources.oauth.clients.base import BaseOAuthClient
|
||||
from authentik.sources.oauth.clients.oauth1 import OAuthClient
|
||||
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class OAuthClientMixin:
|
||||
@ -22,6 +25,9 @@ class OAuthClientMixin:
|
||||
if self.client_class is not None:
|
||||
# pylint: disable=not-callable
|
||||
return self.client_class(source, self.request, **kwargs)
|
||||
if source.request_token_url:
|
||||
return OAuthClient(source, self.request, **kwargs)
|
||||
return OAuth2Client(source, self.request, **kwargs)
|
||||
if source.type.request_token_url or source.request_token_url:
|
||||
client = OAuthClient(source, self.request, **kwargs)
|
||||
else:
|
||||
client = OAuth2Client(source, self.request, **kwargs)
|
||||
LOGGER.debug("Using client for oauth request", client=client)
|
||||
return client
|
||||
|
@ -209,9 +209,9 @@ class OAuthCallback(OAuthClientMixin, View):
|
||||
)
|
||||
return redirect(
|
||||
reverse(
|
||||
"authentik_sources_oauth:oauth-client-user",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
"authentik_core:if-admin",
|
||||
)
|
||||
+ f"#/user;page-{self.source.slug}"
|
||||
)
|
||||
|
||||
def handle_enroll(
|
||||
|
@ -39,13 +39,13 @@ from authentik.sources.saml.processors.constants import (
|
||||
from authentik.sources.saml.processors.request import SESSION_REQUEST_ID
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
from authentik.stages.user_login.stage import DEFAULT_BACKEND
|
||||
|
||||
LOGGER = get_logger()
|
||||
if TYPE_CHECKING:
|
||||
from xml.etree.ElementTree import Element # nosec
|
||||
|
||||
CACHE_SEEN_REQUEST_ID = "authentik_saml_seen_ids_%s"
|
||||
DEFAULT_BACKEND = "django.contrib.auth.backends.ModelBackend"
|
||||
|
||||
|
||||
class ResponseProcessor:
|
||||
|
@ -63,6 +63,7 @@ class EmailStageViewSet(ModelViewSet):
|
||||
"name": value,
|
||||
"description": label,
|
||||
"component": "",
|
||||
"model_name": "",
|
||||
}
|
||||
)
|
||||
return Response(TypeCreateSerializer(choices, many=True).data)
|
||||
|
@ -68,7 +68,7 @@ def send_mail(
|
||||
messages=["Successfully sent Mail."],
|
||||
)
|
||||
)
|
||||
except (SMTPException, ConnectionError) as exc:
|
||||
except (SMTPException, ConnectionError, ValueError) as exc:
|
||||
LOGGER.debug("Error sending email, retrying...", exc=exc)
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
raise exc
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Identification stage logic"""
|
||||
from dataclasses import asdict
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
|
||||
from django.db.models import Q
|
||||
@ -46,6 +47,7 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||
"""Validate that user exists"""
|
||||
pre_user = self.stage.get_user(value)
|
||||
if not pre_user:
|
||||
sleep(0.150)
|
||||
LOGGER.debug("invalid_login", identifier=value)
|
||||
raise ValidationError("Failed to authenticate.")
|
||||
self.pre_user = pre_user
|
||||
@ -68,7 +70,7 @@ class IdentificationStageView(ChallengeStageView):
|
||||
else:
|
||||
model_field += "__exact"
|
||||
query |= Q(**{model_field: uid_value})
|
||||
users = User.objects.filter(query)
|
||||
users = User.objects.filter(query, is_active=True)
|
||||
if users.exists():
|
||||
LOGGER.debug("Found user", user=users.first(), query=query)
|
||||
return users.first()
|
||||
|
@ -3,6 +3,7 @@ from rest_framework.fields import JSONField
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
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
|
||||
@ -29,6 +30,7 @@ class InvitationStageViewSet(ModelViewSet):
|
||||
class InvitationSerializer(ModelSerializer):
|
||||
"""Invitation Serializer"""
|
||||
|
||||
created_by = UserSerializer(read_only=True)
|
||||
fixed_data = JSONField(validators=[is_dict], required=False)
|
||||
|
||||
class Meta:
|
||||
@ -40,7 +42,6 @@ class InvitationSerializer(ModelSerializer):
|
||||
"fixed_data",
|
||||
"created_by",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
class InvitationViewSet(ModelViewSet):
|
||||
|
@ -1,8 +1,11 @@
|
||||
"""invitation stage logic"""
|
||||
from typing import Optional
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.flows.views import SESSION_KEY_GET
|
||||
from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||
from authentik.stages.invitation.signals import invitation_used
|
||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
@ -14,16 +17,26 @@ INVITATION_IN_EFFECT = "invitation_in_effect"
|
||||
class InvitationStageView(StageView):
|
||||
"""Finalise Authentication flow by logging the user in"""
|
||||
|
||||
def get_token(self) -> Optional[str]:
|
||||
"""Get token from saved get-arguments or prompt_data"""
|
||||
if INVITATION_TOKEN_KEY in self.request.session.get(SESSION_KEY_GET, {}):
|
||||
return self.request.session[SESSION_KEY_GET][INVITATION_TOKEN_KEY]
|
||||
if INVITATION_TOKEN_KEY in self.executor.plan.context.get(
|
||||
PLAN_CONTEXT_PROMPT, {}
|
||||
):
|
||||
return self.executor.plan.context[PLAN_CONTEXT_PROMPT][INVITATION_TOKEN_KEY]
|
||||
return None
|
||||
|
||||
def get(self, request: HttpRequest) -> HttpResponse:
|
||||
"""Apply data to the current flow based on a URL"""
|
||||
stage: InvitationStage = self.executor.current_stage
|
||||
if INVITATION_TOKEN_KEY not in request.GET:
|
||||
token = self.get_token()
|
||||
if not token:
|
||||
# No Invitation was given, raise error or continue
|
||||
if stage.continue_flow_without_invitation:
|
||||
return self.executor.stage_ok()
|
||||
return self.executor.stage_invalid()
|
||||
|
||||
token = request.GET[INVITATION_TOKEN_KEY]
|
||||
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
||||
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = invite.fixed_data
|
||||
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
||||
|
@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.http import urlencode
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
@ -94,15 +95,11 @@ class TestUserLoginStage(TestCase):
|
||||
self.stage.continue_flow_without_invitation = False
|
||||
self.stage.save()
|
||||
|
||||
def test_with_invitation(self):
|
||||
def test_with_invitation_get(self):
|
||||
"""Test with invitation, check data in session"""
|
||||
plan = FlowPlan(
|
||||
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()]
|
||||
)
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[
|
||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
] = "django.contrib.auth.backends.ModelBackend"
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
@ -116,9 +113,39 @@ class TestUserLoginStage(TestCase):
|
||||
base_url = reverse(
|
||||
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||
)
|
||||
response = self.client.get(
|
||||
base_url + f"?{INVITATION_TOKEN_KEY}={invite.pk.hex}"
|
||||
args = urlencode({INVITATION_TOKEN_KEY: invite.pk.hex})
|
||||
response = self.client.get(base_url + f"?query={args}")
|
||||
|
||||
session = self.client.session
|
||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
|
||||
)
|
||||
|
||||
def test_with_invitation_prompt_data(self):
|
||||
"""Test with invitation, check data in session"""
|
||||
data = {"foo": "bar"}
|
||||
invite = Invitation.objects.create(
|
||||
created_by=get_anonymous_user(), fixed_data=data
|
||||
)
|
||||
|
||||
plan = FlowPlan(
|
||||
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()]
|
||||
)
|
||||
plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex}
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
with patch("authentik.flows.views.FlowExecutorView.cancel", MagicMock()):
|
||||
base_url = reverse(
|
||||
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||
)
|
||||
response = self.client.get(base_url)
|
||||
|
||||
session = self.client.session
|
||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||
|
@ -11,6 +11,7 @@ from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
|
||||
LOGGER = get_logger()
|
||||
DEFAULT_BACKEND = "django.contrib.auth.backends.ModelBackend"
|
||||
|
||||
|
||||
class UserLoginStageView(StageView):
|
||||
@ -23,12 +24,9 @@ class UserLoginStageView(StageView):
|
||||
messages.error(request, message)
|
||||
LOGGER.debug(message)
|
||||
return self.executor.stage_invalid()
|
||||
if PLAN_CONTEXT_AUTHENTICATION_BACKEND not in self.executor.plan.context:
|
||||
message = _("Pending user has no backend.")
|
||||
messages.error(request, message)
|
||||
LOGGER.debug(message)
|
||||
return self.executor.stage_invalid()
|
||||
backend = self.executor.plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND]
|
||||
backend = self.executor.plan.context.get(
|
||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND, DEFAULT_BACKEND
|
||||
)
|
||||
login(
|
||||
self.request,
|
||||
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER],
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""login tests"""
|
||||
from time import sleep
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import Client, TestCase
|
||||
@ -12,7 +13,6 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||
from authentik.flows.views import SESSION_KEY_PLAN
|
||||
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
from authentik.stages.user_login.models import UserLoginStage
|
||||
|
||||
|
||||
@ -38,9 +38,6 @@ class TestUserLoginStage(TestCase):
|
||||
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()]
|
||||
)
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
plan.context[
|
||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
] = "django.contrib.auth.backends.ModelBackend"
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
@ -55,6 +52,31 @@ class TestUserLoginStage(TestCase):
|
||||
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
|
||||
)
|
||||
|
||||
def test_expiry(self):
|
||||
"""Test with expiry"""
|
||||
self.stage.session_duration = "seconds=2"
|
||||
self.stage.save()
|
||||
plan = FlowPlan(
|
||||
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()]
|
||||
)
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
|
||||
)
|
||||
self.assertNotEqual(list(self.client.session.keys()), [])
|
||||
sleep(3)
|
||||
self.client.session.clear_expired()
|
||||
self.assertEqual(list(self.client.session.keys()), [])
|
||||
|
||||
@patch(
|
||||
"authentik.flows.views.to_stage_response",
|
||||
TO_STAGE_RESPONSE_MOCK,
|
||||
@ -82,32 +104,3 @@ class TestUserLoginStage(TestCase):
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
},
|
||||
)
|
||||
|
||||
@patch(
|
||||
"authentik.flows.views.to_stage_response",
|
||||
TO_STAGE_RESPONSE_MOCK,
|
||||
)
|
||||
def test_without_backend(self):
|
||||
"""Test a plan with pending user, without backend, resulting in a denied"""
|
||||
plan = FlowPlan(
|
||||
flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()]
|
||||
)
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
force_str(response.content),
|
||||
{
|
||||
"component": "ak-stage-access-denied",
|
||||
"error_message": None,
|
||||
"title": "",
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
},
|
||||
)
|
||||
|
@ -34,6 +34,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -50,6 +51,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -66,6 +68,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -82,6 +85,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -104,6 +108,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -128,6 +133,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -157,6 +163,7 @@ stages:
|
||||
# Copy current, latest config to local
|
||||
cp authentik/lib/default.yml local.env.yml
|
||||
git checkout $(git describe --abbrev=0 --match 'version/*')
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -194,6 +201,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -229,6 +237,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -271,6 +280,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev --python python3.9
|
||||
@ -348,6 +358,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
sudo apt update
|
||||
sudo apt install -y libxmlsec1-dev pkg-config
|
||||
sudo pip install -U wheel pipenv
|
||||
pipenv install --dev
|
||||
@ -369,8 +380,6 @@ stages:
|
||||
coverage-unittest/unittest.xml
|
||||
mergeTestResults: true
|
||||
- task: CmdLine@2
|
||||
env:
|
||||
CODECOV_TOKEN: $(CODECOV_TOKEN)
|
||||
inputs:
|
||||
script: bash <(curl -s https://codecov.io/bash)
|
||||
- stage: Build
|
||||
|
@ -20,7 +20,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.2}
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.6}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -48,7 +48,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.2}
|
||||
image: ${AUTHENTIK_IMAGE:-beryju/authentik}:${AUTHENTIK_TAG:-2021.4.6}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
networks:
|
||||
@ -68,7 +68,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
static:
|
||||
image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.4.2}
|
||||
image: ${AUTHENTIK_IMAGE_STATIC:-beryju/authentik-static}:${AUTHENTIK_TAG:-2021.4.6}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal
|
||||
|
@ -3,9 +3,9 @@ description: authentik is an open-source Identity Provider focused on flexibilit
|
||||
name: authentik
|
||||
home: https://goauthentik.io
|
||||
sources:
|
||||
- https://github.com/BeryJu/authentik
|
||||
version: "2021.4.2"
|
||||
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
|
||||
- https://github.com/goauthentik/authentik
|
||||
version: "2021.4.6"
|
||||
icon: https://raw.githubusercontent.com/goauthentik/authentik/master/web/icons/icon.svg
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 9.4.1
|
||||
|
@ -4,12 +4,12 @@
|
||||
|-----------------------------------|-------------------------|-------------|
|
||||
| image.name | beryju/authentik | Image used to run the authentik server and worker |
|
||||
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
|
||||
| image.tag | 2021.4.2 | Image tag |
|
||||
| image.tag | 2021.4.6 | Image tag |
|
||||
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
|
||||
| serverReplicas | 1 | Replicas for the Server deployment |
|
||||
| workerReplicas | 1 | Replicas for the Worker deployment |
|
||||
| kubernetesIntegration | true | Enable/disable the Kubernetes integration for authentik. This will create a service account for authentik to create and update outposts in authentik |
|
||||
| config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` for example. |
|
||||
| config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` or `openssl rand -base64 36` for example. |
|
||||
| config.errorReporting.enabled | false | Enable/disable error reporting |
|
||||
| config.errorReporting.environment | customer | Environment sent with the error reporting |
|
||||
| config.errorReporting.sendPii | false | Whether to send Personally-identifiable data with the error reporting |
|
||||
|
@ -43,29 +43,6 @@ spec:
|
||||
values:
|
||||
- web
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
initContainers:
|
||||
- name: authentik-database-migrations
|
||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
|
||||
args: [migrate]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "authentik.fullname" . }}-config
|
||||
prefix: AUTHENTIK_
|
||||
- secretRef:
|
||||
name: {{ include "authentik.fullname" . }}-secret-key
|
||||
prefix: AUTHENTIK_
|
||||
env:
|
||||
- name: AUTHENTIK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-redis"
|
||||
key: redis-password
|
||||
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||
|
@ -5,7 +5,7 @@ image:
|
||||
name: beryju/authentik
|
||||
name_static: beryju/authentik-static
|
||||
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
|
||||
tag: 2021.4.2
|
||||
tag: 2021.4.6
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
serverReplicas: 1
|
||||
|
@ -2,13 +2,13 @@
|
||||
python -m lifecycle.wait_for_db
|
||||
printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap", "command": "%s"}\n' "$@" > /dev/stderr
|
||||
if [[ "$1" == "server" ]]; then
|
||||
python -m lifecycle.migrate
|
||||
gunicorn -c /lifecycle/gunicorn.conf.py authentik.root.asgi:application
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events
|
||||
elif [[ "$1" == "migrate" ]]; then
|
||||
# Run system migrations first, run normal migrations after
|
||||
printf "DEPERECATED: database migrations are now executed automatically on startup."
|
||||
python -m lifecycle.migrate
|
||||
python -m manage migrate
|
||||
elif [[ "$1" == "backup" ]]; then
|
||||
python -m manage dbbackup --clean
|
||||
elif [[ "$1" == "restore" ]]; then
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
"""System Migration handler"""
|
||||
import os
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from inspect import getmembers, isclass
|
||||
from pathlib import Path
|
||||
@ -11,6 +12,7 @@ from structlog.stdlib import get_logger
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
ADV_LOCK_UID = 1000
|
||||
|
||||
|
||||
class BaseMigration:
|
||||
@ -40,18 +42,36 @@ if __name__ == "__main__":
|
||||
host=CONFIG.y("postgresql.host"),
|
||||
)
|
||||
curr = conn.cursor()
|
||||
# lock an advisory lock to prevent multiple instances from migrating at once
|
||||
LOGGER.info("waiting to acquire database lock")
|
||||
curr.execute("SELECT pg_advisory_lock(%s)", (ADV_LOCK_UID,))
|
||||
try:
|
||||
for migration in (
|
||||
Path(__file__).parent.absolute().glob("system_migrations/*.py")
|
||||
):
|
||||
spec = spec_from_file_location("lifecycle.system_migrations", migration)
|
||||
mod = module_from_spec(spec)
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
for migration in Path(__file__).parent.absolute().glob("system_migrations/*.py"):
|
||||
spec = spec_from_file_location("lifecycle.system_migrations", migration)
|
||||
mod = module_from_spec(spec)
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
for name, sub in getmembers(mod, isclass):
|
||||
if name != "Migration":
|
||||
continue
|
||||
migration = sub(curr, conn)
|
||||
if migration.needs_migration():
|
||||
LOGGER.info("Migration needs to be applied", migration=sub)
|
||||
migration.run()
|
||||
LOGGER.info("Migration finished applying", migration=sub)
|
||||
for name, sub in getmembers(mod, isclass):
|
||||
if name != "Migration":
|
||||
continue
|
||||
migration = sub(curr, conn)
|
||||
if migration.needs_migration():
|
||||
LOGGER.info("Migration needs to be applied", migration=sub)
|
||||
migration.run()
|
||||
LOGGER.info("Migration finished applying", migration=sub)
|
||||
LOGGER.info("applying django migrations")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(["", "migrate"])
|
||||
finally:
|
||||
curr.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))
|
||||
|
@ -1,6 +1,6 @@
|
||||
# authentik outpost
|
||||
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=3)
|
||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=8)
|
||||

|
||||
|
||||
Reverse Proxy based on [oauth2_proxy](https://github.com/oauth2-proxy/oauth2-proxy), completely managed and monitored by authentik.
|
||||
|
@ -41,10 +41,6 @@ stages:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.16.3'
|
||||
- task: Go@0
|
||||
inputs:
|
||||
command: 'get'
|
||||
arguments: '-u golang.org/x/lint/golint'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
@ -53,7 +49,7 @@ stages:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
$(go list -f {{.Target}} golang.org/x/lint/golint) ./...
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.39.0 golangci-lint run -v --timeout 200s
|
||||
workingDirectory: 'outpost/'
|
||||
- stage: proxy_build_go
|
||||
jobs:
|
||||
|
@ -52,13 +52,14 @@ func main() {
|
||||
|
||||
ac.Server = proxy.NewServer(ac)
|
||||
|
||||
ac.Start()
|
||||
err = ac.Start()
|
||||
if err != nil {
|
||||
log.WithError(err).Panic("Failed to run server")
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-interrupt:
|
||||
ac.Shutdown()
|
||||
os.Exit(0)
|
||||
}
|
||||
<-interrupt
|
||||
ac.Shutdown()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ require (
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
github.com/go-openapi/analysis v0.20.1 // indirect
|
||||
github.com/go-openapi/errors v0.20.0
|
||||
github.com/go-openapi/runtime v0.19.27
|
||||
github.com/go-openapi/runtime v0.19.28
|
||||
github.com/go-openapi/strfmt v0.20.1
|
||||
github.com/go-openapi/swag v0.19.15
|
||||
github.com/go-openapi/validate v0.20.2
|
||||
github.com/go-redis/redis/v7 v7.4.0 // indirect
|
||||
github.com/go-swagger/go-swagger v0.27.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/justinas/alice v1.2.0
|
||||
@ -23,6 +23,7 @@ require (
|
||||
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
|
||||
github.com/pelletier/go-toml v1.9.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
|
||||
github.com/recws-org/recws v1.3.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
@ -30,10 +31,11 @@ require (
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.1 // indirect
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 // indirect
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/grpc v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
)
|
||||
|
@ -102,7 +102,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -124,8 +123,6 @@ github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHj
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
|
||||
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@ -157,7 +154,6 @@ github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9sn
|
||||
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
|
||||
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
|
||||
github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
|
||||
github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro=
|
||||
github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
|
||||
github.com/go-openapi/analysis v0.20.1 h1:zdVbw8yoD4SWZeq+cWdGgquaB0W4VrsJvDJHJND/Ktc=
|
||||
github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
|
||||
@ -171,8 +167,6 @@ github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX
|
||||
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.0 h1:Sxpo9PjEHDzhs3FbnGNonvDgWcMW2U7wGTcDDSFSceM=
|
||||
github.com/go-openapi/errors v0.20.0/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
@ -202,8 +196,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
|
||||
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
|
||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
|
||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
|
||||
github.com/go-openapi/runtime v0.19.27 h1:4zrQCJoP7rqNCUaApDv1MdPkaa5TuPfO05Lq5WLhX9I=
|
||||
github.com/go-openapi/runtime v0.19.27/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
|
||||
github.com/go-openapi/runtime v0.19.28 h1:9lYu6axek8LJrVkMVViVirRcpoaCxXX7+sSvmizGVnA=
|
||||
github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
@ -252,9 +246,6 @@ github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-swagger/go-swagger v0.27.0 h1:K7+nkBuf4oS1jTBrdvWqYFpqD69V5CN8HamZzCDDhAI=
|
||||
github.com/go-swagger/go-swagger v0.27.0/go.mod h1:WodZVysInJilkW7e6IRw+dZGp5yW6rlMFZ4cb+THl9A=
|
||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
@ -310,8 +301,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
@ -329,8 +318,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -352,8 +341,6 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
@ -392,8 +379,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
|
||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
@ -521,8 +506,6 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
|
||||
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
||||
@ -538,8 +521,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e h1:BLqxdwZ6j771IpSCRx7s/GJjXHUE00Hmu7/YegCGdzA=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@ -559,7 +540,6 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
@ -586,7 +566,6 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
@ -595,7 +574,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -613,8 +591,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
@ -649,7 +625,6 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
@ -690,8 +665,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
|
||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -715,7 +688,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
@ -725,8 +697,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -767,23 +737,18 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5 h1:zuP3axpB9rV3xH0EA7n3/gCrNPZm2SRl0l4mVH2BRj4=
|
||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
|
||||
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -795,7 +760,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -846,13 +810,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -860,7 +820,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -920,8 +879,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -1005,7 +962,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
@ -30,8 +31,7 @@ type APIController struct {
|
||||
|
||||
Server Outpost
|
||||
|
||||
lastBundleHash string
|
||||
logger *log.Entry
|
||||
logger *log.Entry
|
||||
|
||||
reloadOffset time.Duration
|
||||
|
||||
@ -39,8 +39,8 @@ type APIController struct {
|
||||
}
|
||||
|
||||
// NewAPIController initialise new API Controller instance from URL and API token
|
||||
func NewAPIController(pbURL url.URL, token string) *APIController {
|
||||
transport := httptransport.New(pbURL.Host, client.DefaultBasePath, []string{pbURL.Scheme})
|
||||
func NewAPIController(akURL url.URL, token string) *APIController {
|
||||
transport := httptransport.New(akURL.Host, client.DefaultBasePath, []string{akURL.Scheme})
|
||||
transport.Transport = SetUserAgent(getTLSTransport(), fmt.Sprintf("authentik-proxy@%s", pkg.VERSION))
|
||||
|
||||
// create the transport
|
||||
@ -53,10 +53,11 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
|
||||
|
||||
// Because we don't know the outpost UUID, we simply do a list and pick the first
|
||||
// The service account this token belongs to should only have access to a single outpost
|
||||
outposts, err := apiClient.Outposts.OutpostsOutpostsList(outposts.NewOutpostsOutpostsListParams(), auth)
|
||||
outposts, err := apiClient.Outposts.OutpostsInstancesList(outposts.NewOutpostsInstancesListParams(), auth)
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Panic("Failed to fetch configuration")
|
||||
log.WithError(err).Error("Failed to fetch configuration")
|
||||
os.Exit(1)
|
||||
}
|
||||
outpost := outposts.Payload.Results[0]
|
||||
doGlobalSetup(outpost.Config.(map[string]interface{}))
|
||||
@ -69,18 +70,12 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
|
||||
logger: log,
|
||||
|
||||
reloadOffset: time.Duration(rand.Intn(10)) * time.Second,
|
||||
|
||||
lastBundleHash: "",
|
||||
}
|
||||
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
|
||||
ac.initWS(pbURL, outpost.Pk)
|
||||
ac.initWS(akURL, outpost.Pk)
|
||||
return ac
|
||||
}
|
||||
|
||||
func (a *APIController) GetLastBundleHash() string {
|
||||
return a.lastBundleHash
|
||||
}
|
||||
|
||||
// Start Starts all handlers, non-blocking
|
||||
func (a *APIController) Start() error {
|
||||
err := a.Server.Refresh()
|
||||
@ -96,7 +91,10 @@ func (a *APIController) Start() error {
|
||||
a.startWSHealth()
|
||||
}()
|
||||
go func() {
|
||||
a.Server.Start()
|
||||
err := a.Server.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
package ak
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"goauthentik.io/outpost/pkg/client/outposts"
|
||||
"goauthentik.io/outpost/pkg/models"
|
||||
)
|
||||
@ -15,16 +11,5 @@ func (a *APIController) Update() ([]*models.ProxyOutpostConfig, error) {
|
||||
a.logger.WithError(err).Error("Failed to fetch providers")
|
||||
return nil, err
|
||||
}
|
||||
// Check provider hash to see if anything is changed
|
||||
hasher := sha512.New()
|
||||
out, err := json.Marshal(providers.Payload.Results)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
hash := hex.EncodeToString(hasher.Sum(out))
|
||||
if hash == a.lastBundleHash {
|
||||
return nil, nil
|
||||
}
|
||||
a.lastBundleHash = hash
|
||||
return providers.Payload.Results, nil
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"goauthentik.io/outpost/pkg"
|
||||
)
|
||||
|
||||
func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
|
||||
func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
|
||||
pathTemplate := "%s://%s/ws/outpost/%s/"
|
||||
scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws")
|
||||
scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws")
|
||||
|
||||
authHeader := fmt.Sprintf("Bearer %s", ac.token)
|
||||
|
||||
@ -37,7 +37,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
|
||||
InsecureSkipVerify: strings.ToLower(value) == "true",
|
||||
},
|
||||
}
|
||||
ws.Dial(fmt.Sprintf(pathTemplate, scheme, pbURL.Host, outpostUUID.String()), header)
|
||||
ws.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, outpostUUID.String()), header)
|
||||
|
||||
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithField("outpost", outpostUUID.String()).Debug("connecting to authentik")
|
||||
|
||||
@ -64,7 +64,6 @@ func (ac *APIController) Shutdown() {
|
||||
ac.logger.Println("write close:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ac *APIController) startWSHandler() {
|
||||
@ -92,7 +91,8 @@ func (ac *APIController) startWSHandler() {
|
||||
}
|
||||
|
||||
func (ac *APIController) startWSHealth() {
|
||||
for ; true; <-time.Tick(time.Second * 10) {
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
for ; true; <-ticker.C {
|
||||
if !ac.wsConn.IsConnected() {
|
||||
continue
|
||||
}
|
||||
|
@ -38,7 +38,11 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
||||
return nil
|
||||
}
|
||||
providerOpts := &options.Options{}
|
||||
copier.Copy(&providerOpts, getCommonOptions())
|
||||
err = copier.Copy(&providerOpts, getCommonOptions())
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("Failed to copy options, skipping provider")
|
||||
return nil
|
||||
}
|
||||
providerOpts.ClientID = provider.ClientID
|
||||
providerOpts.ClientSecret = provider.ClientSecret
|
||||
|
||||
@ -62,7 +66,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
||||
ID: "default",
|
||||
URI: *provider.InternalHost,
|
||||
Path: "/",
|
||||
InsecureSkipTLSVerify: *&provider.InternalHostSslValidation,
|
||||
InsecureSkipTLSVerify: provider.InternalHostSslValidation,
|
||||
},
|
||||
}
|
||||
|
||||
@ -85,7 +89,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
|
||||
return providerOpts
|
||||
}
|
||||
|
||||
x509cert, err := tls.X509KeyPair([]byte(*&cert.Payload.Data), []byte(key.Payload.Data))
|
||||
x509cert, err := tls.X509KeyPair([]byte(cert.Payload.Data), []byte(key.Payload.Data))
|
||||
if err != nil {
|
||||
pb.log.WithField("provider", provider.ClientID).WithError(err).Warning("Failed to parse certificate")
|
||||
return providerOpts
|
||||
@ -135,7 +139,7 @@ func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *&provider.BasicAuthEnabled {
|
||||
if provider.BasicAuthEnabled {
|
||||
oauthproxy.SetBasicAuth = true
|
||||
oauthproxy.BasicAuthUserAttribute = provider.BasicAuthUserAttribute
|
||||
oauthproxy.BasicAuthPasswordAttribute = provider.BasicAuthPasswordAttribute
|
||||
|
@ -107,7 +107,7 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
duration := float64(time.Since(t)) / float64(time.Millisecond)
|
||||
h.logger.WithFields(log.Fields{
|
||||
"host": req.RemoteAddr,
|
||||
"vhost": req.Host,
|
||||
"vhost": getHost(req),
|
||||
"request_protocol": req.Proto,
|
||||
"runtime": fmt.Sprintf("%0.3f", duration),
|
||||
"method": req.Method,
|
||||
|
@ -161,7 +161,7 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
|
||||
return
|
||||
}
|
||||
redirectURI := p.GetRedirectURI(req.Host)
|
||||
redirectURI := p.GetRedirectURI(getHost(req))
|
||||
http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), http.StatusFound)
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
session, err := p.redeemCode(req.Context(), req.Host, req.Form.Get("code"))
|
||||
session, err := p.redeemCode(req.Context(), getHost(req), req.Form.Get("code"))
|
||||
if err != nil {
|
||||
p.logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
|
||||
p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
|
||||
|
@ -42,7 +42,8 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
handler, ok := s.Handlers[r.Host]
|
||||
host := getHost(r)
|
||||
handler, ok := s.Handlers[host]
|
||||
if !ok {
|
||||
// If we only have one handler, host name switching doesn't matter
|
||||
if len(s.Handlers) == 1 {
|
||||
@ -56,7 +57,7 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
|
||||
for k := range s.Handlers {
|
||||
hostKeys = append(hostKeys, k)
|
||||
}
|
||||
s.logger.WithField("host", r.Host).WithField("known-hosts", strings.Join(hostKeys, ", ")).Debug("Host header does not match any we know of")
|
||||
s.logger.WithField("host", host).WithField("known-hosts", strings.Join(hostKeys, ", ")).Debug("Host header does not match any we know of")
|
||||
w.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
12
outpost/pkg/proxy/utils.go
Normal file
12
outpost/pkg/proxy/utils.go
Normal file
@ -0,0 +1,12 @@
|
||||
package proxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
var xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
|
||||
|
||||
func getHost(req *http.Request) string {
|
||||
if req.Header.Get(xForwardedHost) != "" {
|
||||
return req.Header.Get(xForwardedHost)
|
||||
}
|
||||
return req.Host
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
package pkg
|
||||
|
||||
const VERSION = "2021.4.2"
|
||||
const VERSION = "2021.4.6"
|
||||
|
517
swagger.yaml
517
swagger.yaml
@ -5,7 +5,7 @@ info:
|
||||
email: hello@beryju.org
|
||||
license:
|
||||
name: GNU GPLv3
|
||||
url: https://github.com/BeryJu/authentik/blob/master/LICENSE
|
||||
url: https://github.com/goauthentik/authentik/blob/master/LICENSE
|
||||
version: v2beta
|
||||
basePath: /api/v2beta
|
||||
consumes:
|
||||
@ -1152,6 +1152,9 @@ paths:
|
||||
description: Page Size
|
||||
required: false
|
||||
type: integer
|
||||
- name: superuser_full_list
|
||||
in: query
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
@ -4366,6 +4369,281 @@ paths:
|
||||
description: A unique integer value identifying this OAuth2 Token.
|
||||
required: true
|
||||
type: integer
|
||||
/outposts/instances/:
|
||||
get:
|
||||
operationId: outposts_instances_list
|
||||
description: Outpost Viewset
|
||||
parameters:
|
||||
- name: providers__isnull
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
required: false
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: Page Index
|
||||
required: false
|
||||
type: integer
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Page Size
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
required:
|
||||
- results
|
||||
- pagination
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
required:
|
||||
- next
|
||||
- previous
|
||||
- count
|
||||
- current
|
||||
- total_pages
|
||||
- start_index
|
||||
- end_index
|
||||
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
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Outpost'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- outposts
|
||||
post:
|
||||
operationId: outposts_instances_create
|
||||
description: Outpost Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- outposts
|
||||
parameters: []
|
||||
/outposts/instances/default_settings/:
|
||||
get:
|
||||
operationId: outposts_instances_default_settings
|
||||
description: Global default outpost config
|
||||
parameters:
|
||||
- name: providers__isnull
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
required: false
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: Page Index
|
||||
required: false
|
||||
type: integer
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Page Size
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/OutpostDefaultConfig'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
tags:
|
||||
- outposts
|
||||
parameters: []
|
||||
/outposts/instances/{uuid}/:
|
||||
get:
|
||||
operationId: outposts_instances_read
|
||||
description: Outpost Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
'404':
|
||||
description: Object does not exist or caller has insufficient permissions
|
||||
to access it.
|
||||
schema:
|
||||
$ref: '#/definitions/APIException'
|
||||
tags:
|
||||
- outposts
|
||||
put:
|
||||
operationId: outposts_instances_update
|
||||
description: Outpost Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
'404':
|
||||
description: Object does not exist or caller has insufficient permissions
|
||||
to access it.
|
||||
schema:
|
||||
$ref: '#/definitions/APIException'
|
||||
tags:
|
||||
- outposts
|
||||
patch:
|
||||
operationId: outposts_instances_partial_update
|
||||
description: Outpost Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Outpost'
|
||||
'400':
|
||||
description: Invalid input.
|
||||
schema:
|
||||
$ref: '#/definitions/ValidationError'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
'404':
|
||||
description: Object does not exist or caller has insufficient permissions
|
||||
to access it.
|
||||
schema:
|
||||
$ref: '#/definitions/APIException'
|
||||
tags:
|
||||
- outposts
|
||||
delete:
|
||||
operationId: outposts_instances_delete
|
||||
description: Outpost Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
'404':
|
||||
description: Object does not exist or caller has insufficient permissions
|
||||
to access it.
|
||||
schema:
|
||||
$ref: '#/definitions/APIException'
|
||||
tags:
|
||||
- outposts
|
||||
parameters:
|
||||
- name: uuid
|
||||
in: path
|
||||
description: A UUID string identifying this outpost.
|
||||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
/outposts/instances/{uuid}/health/:
|
||||
get:
|
||||
operationId: outposts_instances_health
|
||||
description: Get outposts current health
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/OutpostHealth'
|
||||
'403':
|
||||
description: Authentication credentials were invalid, absent or insufficient.
|
||||
schema:
|
||||
$ref: '#/definitions/GenericError'
|
||||
'404':
|
||||
description: Object does not exist or caller has insufficient permissions
|
||||
to access it.
|
||||
schema:
|
||||
$ref: '#/definitions/APIException'
|
||||
tags:
|
||||
- outposts
|
||||
parameters:
|
||||
- name: uuid
|
||||
in: path
|
||||
description: A UUID string identifying this outpost.
|
||||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
/outposts/outposts/:
|
||||
get:
|
||||
operationId: outposts_outposts_list
|
||||
@ -7671,6 +7949,9 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/PolicyTest'
|
||||
- name: format_result
|
||||
in: query
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
@ -14771,6 +15052,7 @@ definitions:
|
||||
- name
|
||||
- description
|
||||
- component
|
||||
- model_name
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
@ -14785,6 +15067,10 @@ definitions:
|
||||
title: Component
|
||||
type: string
|
||||
minLength: 1
|
||||
model_name:
|
||||
title: Model name
|
||||
type: string
|
||||
minLength: 1
|
||||
EventTopPerUser:
|
||||
required:
|
||||
- application
|
||||
@ -17029,21 +17315,29 @@ definitions:
|
||||
for OAuth 1.
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
authorization_url:
|
||||
title: Authorization URL
|
||||
description: URL the user is redirect to to conest the flow.
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
access_token_url:
|
||||
title: Access Token URL
|
||||
description: URL used by authentik to retrive tokens.
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
profile_url:
|
||||
title: Profile URL
|
||||
description: URL used by authentik to get user information.
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
consumer_key:
|
||||
title: Consumer key
|
||||
type: string
|
||||
@ -17723,226 +18017,7 @@ definitions:
|
||||
title: Fixed data
|
||||
type: object
|
||||
created_by:
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
- name
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
password:
|
||||
title: Password
|
||||
type: string
|
||||
maxLength: 128
|
||||
minLength: 1
|
||||
last_login:
|
||||
title: Last login
|
||||
type: string
|
||||
format: date-time
|
||||
x-nullable: true
|
||||
username:
|
||||
title: Username
|
||||
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
|
||||
only.
|
||||
type: string
|
||||
pattern: ^[\w.@+-]+$
|
||||
maxLength: 150
|
||||
minLength: 1
|
||||
first_name:
|
||||
title: First name
|
||||
type: string
|
||||
maxLength: 150
|
||||
last_name:
|
||||
title: Last name
|
||||
type: string
|
||||
maxLength: 150
|
||||
email:
|
||||
title: Email address
|
||||
type: string
|
||||
format: email
|
||||
maxLength: 254
|
||||
is_active:
|
||||
title: Active
|
||||
description: Designates whether this user should be treated as active.
|
||||
Unselect this instead of deleting accounts.
|
||||
type: boolean
|
||||
date_joined:
|
||||
title: Date joined
|
||||
type: string
|
||||
format: date-time
|
||||
uuid:
|
||||
title: Uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
description: User's display name.
|
||||
type: string
|
||||
minLength: 1
|
||||
password_change_date:
|
||||
title: Password change date
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
groups:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
maxLength: 150
|
||||
minLength: 1
|
||||
permissions:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
uniqueItems: true
|
||||
readOnly: true
|
||||
user_permissions:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
- codename
|
||||
- content_type
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
codename:
|
||||
title: Codename
|
||||
type: string
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
content_type:
|
||||
title: Content type
|
||||
type: integer
|
||||
readOnly: true
|
||||
sources:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
- slug
|
||||
type: object
|
||||
properties:
|
||||
pbm_uuid:
|
||||
title: Pbm uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
policy_engine_mode:
|
||||
title: Policy engine mode
|
||||
type: string
|
||||
enum:
|
||||
- all
|
||||
- any
|
||||
managed:
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects
|
||||
are created and updated automatically. This is flag only indicates
|
||||
that an object can be overwritten by migrations. You can still
|
||||
modify the objects via the API, but expect changes to be overwritten
|
||||
in a later update.
|
||||
type: string
|
||||
minLength: 1
|
||||
x-nullable: true
|
||||
name:
|
||||
title: Name
|
||||
description: Source's display Name.
|
||||
type: string
|
||||
minLength: 1
|
||||
slug:
|
||||
title: Slug
|
||||
description: Internal source name, used in URLs.
|
||||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
maxLength: 50
|
||||
minLength: 1
|
||||
enabled:
|
||||
title: Enabled
|
||||
type: boolean
|
||||
authentication_flow:
|
||||
title: Authentication flow
|
||||
description: Flow to use when authenticating existing users.
|
||||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
enrollment_flow:
|
||||
title: Enrollment flow
|
||||
description: Flow to use when enrolling new users.
|
||||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
policies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
uniqueItems: true
|
||||
property_mappings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
uniqueItems: true
|
||||
readOnly: true
|
||||
ak_groups:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
- parent
|
||||
type: object
|
||||
properties:
|
||||
group_uuid:
|
||||
title: Group uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
maxLength: 80
|
||||
minLength: 1
|
||||
is_superuser:
|
||||
title: Is superuser
|
||||
description: Users added to this group will be superusers.
|
||||
type: boolean
|
||||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
parent:
|
||||
title: Parent
|
||||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
$ref: '#/definitions/User'
|
||||
InvitationStage:
|
||||
required:
|
||||
- name
|
||||
|
@ -13,13 +13,13 @@ from selenium.webdriver.common.by import By
|
||||
from authentik import __version__
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
Outpost,
|
||||
OutpostConfig,
|
||||
OutpostType,
|
||||
)
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
|
||||
@ -117,7 +117,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
@object_manager
|
||||
def test_proxy_connectivity(self):
|
||||
"""Test proxy connectivity over websocket"""
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
outpost_local_connection()
|
||||
proxy: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="proxy_provider",
|
||||
authorization_flow=Flow.objects.get(
|
||||
|
@ -4,6 +4,7 @@ from sys import platform
|
||||
from time import sleep
|
||||
from typing import Any, Optional
|
||||
from unittest.case import skipUnless
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.test import override_settings
|
||||
from docker.models.containers import Container
|
||||
@ -22,12 +23,31 @@ from authentik.providers.oauth2.generators import (
|
||||
generate_client_secret,
|
||||
)
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.manager import SourceType
|
||||
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
|
||||
CONFIG_PATH = "/tmp/dex.yml" # nosec
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class OAUth1Type(SourceType):
|
||||
"""Twitter Type definition"""
|
||||
|
||||
callback_view = TwitterOAuthCallback
|
||||
name = "Twitter"
|
||||
slug = "twitter"
|
||||
|
||||
request_token_url = "http://localhost:5000/oauth/request_token" # nosec
|
||||
access_token_url = "http://localhost:5000/oauth/access_token" # nosec
|
||||
authorization_url = "http://localhost:5000/oauth/authorize"
|
||||
profile_url = "http://localhost:5000/api/me"
|
||||
urls_customizable = False
|
||||
|
||||
|
||||
SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type())
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
class TestSourceOAuth2(SeleniumTestCase):
|
||||
"""test OAuth Source flow"""
|
||||
@ -291,10 +311,6 @@ class TestSourceOAuth1(SeleniumTestCase):
|
||||
authentication_flow=authentication_flow,
|
||||
enrollment_flow=enrollment_flow,
|
||||
provider_type="twitter",
|
||||
request_token_url="http://localhost:5000/oauth/request_token",
|
||||
access_token_url="http://localhost:5000/oauth/access_token",
|
||||
authorization_url="http://localhost:5000/oauth/authorize",
|
||||
profile_url="http://localhost:5000/api/me",
|
||||
consumer_key=self.client_id,
|
||||
consumer_secret=self.client_secret,
|
||||
)
|
||||
@ -304,6 +320,10 @@ class TestSourceOAuth1(SeleniumTestCase):
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@patch(
|
||||
"authentik.sources.oauth.types.manager.SourceTypeManager.find_type",
|
||||
SOURCE_TYPE_MOCK,
|
||||
)
|
||||
@object_manager
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
|
@ -12,9 +12,9 @@ from docker.types.healthcheck import Healthcheck
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.controllers.docker import DockerController
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ class OutpostDockerTests(TestCase):
|
||||
self.ssl_folder = mkdtemp()
|
||||
self.container = self._start_container(self.ssl_folder)
|
||||
# Ensure that local connection have been created
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
outpost_local_connection()
|
||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
|
@ -3,11 +3,11 @@ from django.test import TestCase
|
||||
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.controllers.k8s.base import NeedsUpdate
|
||||
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ class OutpostKubernetesTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Ensure that local connection have been created
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
outpost_local_connection()
|
||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
|
@ -12,8 +12,8 @@ from docker.types.healthcheck import Healthcheck
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.controllers.docker import DockerController
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
@ -53,7 +53,7 @@ class TestProxyDocker(TestCase):
|
||||
self.ssl_folder = mkdtemp()
|
||||
self.container = self._start_container(self.ssl_folder)
|
||||
# Ensure that local connection have been created
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
outpost_local_connection()
|
||||
self.provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="test",
|
||||
internal_host="http://localhost",
|
||||
|
@ -3,8 +3,8 @@ import yaml
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.apps import AuthentikOutpostConfig
|
||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
@ -14,7 +14,7 @@ class TestProxyKubernetes(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Ensure that local connection have been created
|
||||
AuthentikOutpostConfig.init_local_connection()
|
||||
outpost_local_connection()
|
||||
|
||||
def test_kubernetes_controller_static(self):
|
||||
"""Test Kubernetes Controller"""
|
||||
|
1
web/authentik/sources/plex.svg
Normal file
1
web/authentik/sources/plex.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title/><path d="M11.643 0H4.68l7.679 12L4.68 24h6.963l7.677-12-7.677-12"/></svg>
|
After Width: | Height: | Size: 175 B |
@ -81,7 +81,7 @@ http {
|
||||
location /static/ {
|
||||
expires 31d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
add_header X-authentik-version "2021.4.2";
|
||||
add_header X-authentik-version "2021.4.6";
|
||||
add_header Vary X-authentik-version;
|
||||
}
|
||||
|
||||
|
399
web/package-lock.json
generated
399
web/package-lock.json
generated
@ -26,19 +26,19 @@
|
||||
"integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz",
|
||||
"integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==",
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.16.tgz",
|
||||
"integrity": "sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-compilation-targets": "^7.13.13",
|
||||
"@babel/generator": "^7.13.16",
|
||||
"@babel/helper-compilation-targets": "^7.13.16",
|
||||
"@babel/helper-module-transforms": "^7.13.14",
|
||||
"@babel/helpers": "^7.13.10",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/helpers": "^7.13.16",
|
||||
"@babel/parser": "^7.13.16",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"@babel/types": "^7.13.16",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@ -55,6 +55,32 @@
|
||||
"@babel/highlight": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz",
|
||||
"integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA=="
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.16.tgz",
|
||||
"integrity": "sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.13.16",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz",
|
||||
"integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.13.15",
|
||||
"@babel/helper-validator-option": "^7.12.17",
|
||||
"browserslist": "^4.14.5",
|
||||
"semver": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
@ -71,25 +97,34 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
|
||||
"integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ=="
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.16.tgz",
|
||||
"integrity": "sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw=="
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
|
||||
"integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.17.tgz",
|
||||
"integrity": "sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/generator": "^7.13.16",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"@babel/parser": "^7.13.16",
|
||||
"@babel/types": "^7.13.17",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.17.tgz",
|
||||
"integrity": "sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@ -355,13 +390,87 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz",
|
||||
"integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==",
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.17.tgz",
|
||||
"integrity": "sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.13.0",
|
||||
"@babel/types": "^7.13.0"
|
||||
"@babel/traverse": "^7.13.17",
|
||||
"@babel/types": "^7.13.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.16.tgz",
|
||||
"integrity": "sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.13.16",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
|
||||
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.16",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.16.tgz",
|
||||
"integrity": "sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw=="
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.17.tgz",
|
||||
"integrity": "sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.16",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.16",
|
||||
"@babel/types": "^7.13.17",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.17.tgz",
|
||||
"integrity": "sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
@ -1336,86 +1445,28 @@
|
||||
}
|
||||
},
|
||||
"@lingui/babel-plugin-extract-messages": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.8.9.tgz",
|
||||
"integrity": "sha512-zPpSl89nvUrLyGHfVosZHCP9fylfCfkEMc29wGdjE6f0U+frJ59NRLilWMy7xaE8uz97cD5vkhYaaF1wnavhxA==",
|
||||
"version": "3.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.8.10.tgz",
|
||||
"integrity": "sha512-16EnNRb1HXNjdDLMY3xS7jh0wKA00x21LC1CIKRAki80u92jvkSMOJYk+lD6yhdrcl0dH5OMAbdluAm1+rpEPw==",
|
||||
"requires": {
|
||||
"@babel/generator": "^7.11.6",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@lingui/conf": "^3.8.9",
|
||||
"@lingui/conf": "^3.8.10",
|
||||
"mkdirp": "^1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lingui/conf": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
|
||||
"integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"jest-validate": "^26.5.2",
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@lingui/cli": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.8.9.tgz",
|
||||
"integrity": "sha512-UccLtfwrTjXrZcTxpqA4ggYhuUMbXZtzbUVks8nDVt3emVqU56C3VMvVD8WKXLL8Qmq9cEDXPwIZy7IKRL4mEQ==",
|
||||
"version": "3.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-3.8.10.tgz",
|
||||
"integrity": "sha512-YLkT5e6JRwVcXEwLD0++/m1p/wvRQbLj/+m8geXfrcFfrsQyT3uhHNZRFK0GdsjyDslSqJYbalYibJUbgC2sOA==",
|
||||
"requires": {
|
||||
"@babel/generator": "^7.11.6",
|
||||
"@babel/parser": "^7.11.5",
|
||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@babel/types": "^7.11.5",
|
||||
"@lingui/babel-plugin-extract-messages": "^3.8.9",
|
||||
"@lingui/conf": "^3.8.9",
|
||||
"@lingui/babel-plugin-extract-messages": "^3.8.10",
|
||||
"@lingui/conf": "^3.8.10",
|
||||
"babel-plugin-macros": "^3.0.1",
|
||||
"bcp-47": "^1.0.7",
|
||||
"chalk": "^4.1.0",
|
||||
@ -1442,19 +1493,6 @@
|
||||
"ramda": "^0.27.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lingui/conf": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
|
||||
"integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"jest-validate": "^26.5.2",
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@ -1531,9 +1569,9 @@
|
||||
}
|
||||
},
|
||||
"@lingui/conf": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.9.tgz",
|
||||
"integrity": "sha512-r0RGchwiALjCE6CSOtOKbOqVrNg1EQ78AXjyvbrtJoPWVlChDasWCckXEF0BSnsoZaRP6nQCAI+dsQiGW1deWg==",
|
||||
"version": "3.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-3.8.10.tgz",
|
||||
"integrity": "sha512-4KdH+23WXZ5g+LRlvvise3z3mdd41zLgqSJ/PUCMGk60RfElvTrTdxpnm2tOF/2hr+OyGCQEy6kLq606y639qw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@endemolshinegroup/cosmiconfig-typescript-loader": "^3.0.2",
|
||||
@ -1589,9 +1627,9 @@
|
||||
}
|
||||
},
|
||||
"@lingui/core": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.8.9.tgz",
|
||||
"integrity": "sha512-QmEfgukR7w/4/4USZT0LGNt7Yq/RgirFl4088wEta0vgroidxaCRgUXr8RXcdFVjTdtG5dc86JTEj4inZECKvg==",
|
||||
"version": "3.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.8.10.tgz",
|
||||
"integrity": "sha512-1OzZW8iP5yAXxz49pY/WZ1acLvkekd6HgDh8zH3jMA2Hbig2jk6VGVERMO7lwEwJiyEuxaQpe8fRrhCTB7wA3A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"make-plural": "^6.2.2",
|
||||
@ -1599,12 +1637,12 @@
|
||||
}
|
||||
},
|
||||
"@lingui/macro": {
|
||||
"version": "3.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.8.9.tgz",
|
||||
"integrity": "sha512-9LhlbkJ9wOtOLhlaVRLHCRL55S5wOFyyqEhUM+ujUmCskTmMmXzjnRsw5f11nJTK1JJETMT/VlUB5/p7D7Edkw==",
|
||||
"version": "3.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-3.8.10.tgz",
|
||||
"integrity": "sha512-oZZ/F7HsNQkDsnHFroxzGFuEIXM624H72RIj8j2ClpR64nt+xYDxXYC6TYFicQLtBGcKKBTBoM+zbDaoIv74qQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@lingui/conf": "^3.8.9",
|
||||
"@lingui/conf": "^3.8.10",
|
||||
"ramda": "^0.27.1"
|
||||
}
|
||||
},
|
||||
@ -1828,13 +1866,13 @@
|
||||
}
|
||||
},
|
||||
"@sentry/browser": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.2.5.tgz",
|
||||
"integrity": "sha512-nlvaE+D7oaj4MxoY9ikw+krQDOjftnDYJQnOwOraXPk7KYM6YwmkakLuE+x/AkaH3FQVTQF330VAa9d6SWETlA==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.3.0.tgz",
|
||||
"integrity": "sha512-Rse9j5XwN9n7GnfW1mNscTS4YQ0oiBNJcaSk3Mw/vQT872Wh60yKyx5wxAw5GujFZI0NgdyPlZwZ/tGQwirRxA==",
|
||||
"requires": {
|
||||
"@sentry/core": "6.2.5",
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/utils": "6.2.5",
|
||||
"@sentry/core": "6.3.0",
|
||||
"@sentry/types": "6.3.0",
|
||||
"@sentry/utils": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1846,14 +1884,14 @@
|
||||
}
|
||||
},
|
||||
"@sentry/core": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.2.5.tgz",
|
||||
"integrity": "sha512-I+AkgIFO6sDUoHQticP6I27TT3L+i6TUS03in3IEtpBcSeP2jyhlxI8l/wdA7gsBqUPdQ4GHOOaNgtFIcr8qag==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.3.0.tgz",
|
||||
"integrity": "sha512-voot/lJ9gRXB6bx6tVqbEbD6jOd4Sx6Rfmm6pzfpom9C0q+fjIZTatTLq8GdXj8DzxaH1MBDSwtaq/eC3NqYpA==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.2.5",
|
||||
"@sentry/minimal": "6.2.5",
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/utils": "6.2.5",
|
||||
"@sentry/hub": "6.3.0",
|
||||
"@sentry/minimal": "6.3.0",
|
||||
"@sentry/types": "6.3.0",
|
||||
"@sentry/utils": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1865,12 +1903,12 @@
|
||||
}
|
||||
},
|
||||
"@sentry/hub": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.5.tgz",
|
||||
"integrity": "sha512-YlEFdEhcfqpl2HC+/dWXBsBJEljyMzFS7LRRjCk8QANcOdp9PhwQjwebUB4/ulOBjHPP2WZk7fBBd/IKDasTUg==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.3.0.tgz",
|
||||
"integrity": "sha512-lAnW3Om66t9IR+t1wya1NpOF9lGbvYG6Ca8wxJJGJ1t2PxKwyxpZKzRx0q8M1QFhlZ5cETCzxmM7lBEZ4QVCBg==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/utils": "6.2.5",
|
||||
"@sentry/types": "6.3.0",
|
||||
"@sentry/utils": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1882,12 +1920,12 @@
|
||||
}
|
||||
},
|
||||
"@sentry/minimal": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.5.tgz",
|
||||
"integrity": "sha512-RKP4Qx3p7Cv0oX1cPKAkNVFYM7p2k1t32cNk1+rrVQS4hwlJ7Eg6m6fsqsO+85jd6Ne/FnyYsfo9cDD3ImTlWQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.3.0.tgz",
|
||||
"integrity": "sha512-ZdPUwdPQkaKroy67NkwQRqmnfKyd/C1OyouM9IqYKyBjAInjOijwwc/Rd91PMHalvCOGfp1scNZYbZ+YFs/qQQ==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.2.5",
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/hub": "6.3.0",
|
||||
"@sentry/types": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1899,17 +1937,51 @@
|
||||
}
|
||||
},
|
||||
"@sentry/tracing": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.5.tgz",
|
||||
"integrity": "sha512-j/hM0BoHxfrNLxPeEJ5Vq4R34hO/TOHMEpLR3FdnunBXbsmjoKMMygIkPxnpML5XWtvukAehbwpDXldwMYz83w==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.3.0.tgz",
|
||||
"integrity": "sha512-3UNGgQOrDKBoDqLc4vt+0n27Zv3lbNEoCbBydq4IvGfuYq7ozWMsaTcelsotMsd4ckDuOEh8V/nJTqrDjvL76g==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.2.5",
|
||||
"@sentry/minimal": "6.2.5",
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/utils": "6.2.5",
|
||||
"@sentry/hub": "6.3.0",
|
||||
"@sentry/minimal": "6.3.0",
|
||||
"@sentry/types": "6.3.0",
|
||||
"@sentry/utils": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/hub": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.3.0.tgz",
|
||||
"integrity": "sha512-lAnW3Om66t9IR+t1wya1NpOF9lGbvYG6Ca8wxJJGJ1t2PxKwyxpZKzRx0q8M1QFhlZ5cETCzxmM7lBEZ4QVCBg==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.3.0",
|
||||
"@sentry/utils": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/minimal": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.3.0.tgz",
|
||||
"integrity": "sha512-ZdPUwdPQkaKroy67NkwQRqmnfKyd/C1OyouM9IqYKyBjAInjOijwwc/Rd91PMHalvCOGfp1scNZYbZ+YFs/qQQ==",
|
||||
"requires": {
|
||||
"@sentry/hub": "6.3.0",
|
||||
"@sentry/types": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.3.0.tgz",
|
||||
"integrity": "sha512-xWyCYDmFPjS5ex60kxOOHbHEs4vs00qHbm0iShQfjl4OSg9S2azkcWofDmX8Xbn0FSOUXgdPCjNJW1B0bPVhCA=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.3.0.tgz",
|
||||
"integrity": "sha512-NZzw4oLelgvCsVBG2e+ZtFtaBvgA7rZYtcGFbZTphhAlYoJ6JMCQUzYk0iwJK79yR1quh510x4UE0jynvvToWg==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@ -1918,16 +1990,16 @@
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.5.tgz",
|
||||
"integrity": "sha512-1Sux6CLYrV9bETMsGP/HuLFLouwKoX93CWzG8BjMueW+Di0OGxZphYjXrGuDs8xO8bAKEVGCHgVQdcB2jevS0w=="
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.3.0.tgz",
|
||||
"integrity": "sha512-xWyCYDmFPjS5ex60kxOOHbHEs4vs00qHbm0iShQfjl4OSg9S2azkcWofDmX8Xbn0FSOUXgdPCjNJW1B0bPVhCA=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.5.tgz",
|
||||
"integrity": "sha512-fJoLUZHrd5MPylV1dT4qL74yNFDl1Ur/dab+pKNSyvnHPnbZ/LRM7aJ8VaRY/A7ZdpRowU+E14e/Yeem2c6gtQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.3.0.tgz",
|
||||
"integrity": "sha512-NZzw4oLelgvCsVBG2e+ZtFtaBvgA7rZYtcGFbZTphhAlYoJ6JMCQUzYk0iwJK79yR1quh510x4UE0jynvvToWg==",
|
||||
"requires": {
|
||||
"@sentry/types": "6.2.5",
|
||||
"@sentry/types": "6.3.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -1956,9 +2028,9 @@
|
||||
}
|
||||
},
|
||||
"@types/codemirror": {
|
||||
"version": "0.0.108",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz",
|
||||
"integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==",
|
||||
"version": "0.0.109",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
|
||||
"integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
|
||||
"requires": {
|
||||
"@types/tern": "*"
|
||||
}
|
||||
@ -2231,6 +2303,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.10.2.tgz",
|
||||
"integrity": "sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A=="
|
||||
},
|
||||
"@webcomponents/webcomponentsjs": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.5.0.tgz",
|
||||
"integrity": "sha512-C0l51MWQZ9kLzcxOZtniOMohpIFdCLZum7/TEHv3XWFc1Fvt5HCpbSX84x8ltka/JuNKcuiDnxXFkiB2gaePcg=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
@ -2647,9 +2724,9 @@
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.1.0.tgz",
|
||||
"integrity": "sha512-bKJi2VbC4fqZXlLbK7LKVvmG9crjoG9anfp96utZLyIGPuCx+YN+5/HDXy98QGt3lf74T8gKUPISUZL222tDJQ=="
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.1.1.tgz",
|
||||
"integrity": "sha512-ghNJersc9VD9MECwa5bL8gqvCkndW6RSCicdEHL9lIriNtXwKawlSmwo+u6KNXLYT2+f24GdFPBoynKW3ke4MQ=="
|
||||
},
|
||||
"chartjs-adapter-moment": {
|
||||
"version": "1.0.0",
|
||||
@ -2775,9 +2852,9 @@
|
||||
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.60.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz",
|
||||
"integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA=="
|
||||
"version": "5.61.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.0.tgz",
|
||||
"integrity": "sha512-D3wYH90tYY1BsKlUe0oNj2JAhQ9TepkD51auk3N7q+4uz7A/cgJ5JsWHreT0PqieW1QhOuqxQ2reCXV1YXzecg=="
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
@ -2921,9 +2998,9 @@
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.20.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.1.tgz",
|
||||
"integrity": "sha512-8P5M8Kxbnovd0zfvOs7ipkiVJ3/zZQ0F/nrBW4x5E+I0uAZVZ80h6CKd24fSXQ5TLK5hXMtI4yb2O5rEZdUt2A=="
|
||||
"version": "2.21.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.1.tgz",
|
||||
"integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.1",
|
||||
|
@ -35,34 +35,35 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.15",
|
||||
"@babel/core": "^7.13.16",
|
||||
"@babel/plugin-proposal-decorators": "^7.13.15",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@lingui/cli": "^3.8.9",
|
||||
"@lingui/core": "^3.8.9",
|
||||
"@lingui/macro": "^3.8.9",
|
||||
"@lingui/cli": "^3.8.10",
|
||||
"@lingui/core": "^3.8.10",
|
||||
"@lingui/macro": "^3.8.10",
|
||||
"@patternfly/patternfly": "^4.96.2",
|
||||
"@polymer/iron-form": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@sentry/browser": "^6.2.5",
|
||||
"@sentry/tracing": "^6.2.5",
|
||||
"@sentry/browser": "^6.3.0",
|
||||
"@sentry/tracing": "^6.3.0",
|
||||
"@types/chart.js": "^2.9.32",
|
||||
"@types/codemirror": "0.0.108",
|
||||
"@types/codemirror": "0.0.109",
|
||||
"@types/grecaptcha": "^3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.5.0",
|
||||
"authentik-api": "file:api",
|
||||
"babel-plugin-macros": "^3.0.1",
|
||||
"base64-js": "^1.5.1",
|
||||
"chart.js": "^3.1.0",
|
||||
"chart.js": "^3.1.1",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"codemirror": "^5.60.0",
|
||||
"codemirror": "^5.61.0",
|
||||
"construct-style-sheets-polyfill": "^2.4.16",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
|
2
web/poly.ts
Normal file
2
web/poly.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import "construct-style-sheets-polyfill";
|
||||
import "@webcomponents/webcomponentsjs";
|
@ -76,9 +76,7 @@ export default [
|
||||
},
|
||||
// Polyfills (imported first)
|
||||
{
|
||||
input: [
|
||||
"construct-style-sheets-polyfill"
|
||||
],
|
||||
input: "./poly.ts",
|
||||
output: [
|
||||
{
|
||||
format: "iife",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user