Compare commits
	
		
			1000 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2397cb162a | |||
| 80bcd09cec | |||
| 1e10f37370 | |||
| bf253643a6 | |||
| ab4569e5d6 | |||
| 8df29235bb | |||
| cb048764f4 | |||
| 5627848fad | |||
| fb53dc826a | |||
| 335c5a0b80 | |||
| d76db3caba | |||
| 32d88c3a49 | |||
| 5522c94b65 | |||
| 19e73630ab | |||
| 97364ad102 | |||
| 55fd7cd151 | |||
| c9cc1629d6 | |||
| f4ec678587 | |||
| 115274e691 | |||
| 96d3d536be | |||
| f156c0f05d | |||
| 5d64b0cafd | |||
| 182256c53e | |||
| c44aa2a204 | |||
| c133f16371 | |||
| ca2a4ffb59 | |||
| 75bc7c1cbd | |||
| 7c761ff3d9 | |||
| f6b8dc5cea | |||
| 6f7fb4c919 | |||
| 1fbf6be6c2 | |||
| f3aea29324 | |||
| f5921f8480 | |||
| c82cd4fbcf | |||
| 83bb3f8b0b | |||
| c887139367 | |||
| 34b8a97ae9 | |||
| 5dd29d45d8 | |||
| 43ad4f58ac | |||
| 23f269d676 | |||
| e7346317bb | |||
| 98318953cd | |||
| 5a5a32ff83 | |||
| 232a5a8ad0 | |||
| 6049d91f7c | |||
| 118f55d95c | |||
| 1494394a78 | |||
| 963af1ac1e | |||
| e7b7186f4b | |||
| 33fb06a299 | |||
| 66e0c545ac | |||
| 1fbc7ed5fa | |||
| 9c081ae417 | |||
| 17faffd78e | |||
| 16885b064e | |||
| 65bee361a2 | |||
| aff192dbbe | |||
| d37c33d941 | |||
| 7b0005ac42 | |||
| aefeb5bacf | |||
| 7d0e7bcf75 | |||
| dbc75428a0 | |||
| e33a1ea0c7 | |||
| ca35204e0c | |||
| 4a74d16388 | |||
| 3c47555276 | |||
| c5abecf578 | |||
| 8793bb1358 | |||
| 37632bd0c7 | |||
| fb09c8f863 | |||
| f14d0aade4 | |||
| 29eda41eed | |||
| 5eaead60b6 | |||
| 4054e6da8c | |||
| 12b1f53948 | |||
| 35232afa7e | |||
| 17de0ff24e | |||
| c5b56fd4e6 | |||
| 8f20376804 | |||
| a2a35e49a9 | |||
| fb409a73a1 | |||
| a13d89fcde | |||
| a31fc8319d | |||
| b09943e106 | |||
| d5169504ea | |||
| e678e3553b | |||
| 4b2119510c | |||
| e903582f96 | |||
| 20de845f2b | |||
| 5fc052a384 | |||
| 7b523d8be2 | |||
| af15e32d30 | |||
| b6900e498c | |||
| dfc1cc08bb | |||
| 80e426a4b8 | |||
| 2196468804 | |||
| 5ccbc17e65 | |||
| b98b4f2ae7 | |||
| dcc873b88b | |||
| d48badbca3 | |||
| f0ef2eea4f | |||
| 61652406c7 | |||
| 11859c8cea | |||
| a6608c140e | |||
| 3da23829d3 | |||
| ab8c954e00 | |||
| c89ec88751 | |||
| c0dbb738bd | |||
| d0230c0b54 | |||
| a9336d0983 | |||
| 2c4239d79a | |||
| 1a0a62975c | |||
| e06d729fe5 | |||
| a66b832154 | |||
| b2189374e2 | |||
| ff40ab0c49 | |||
| 002c048d0b | |||
| 52029f55e4 | |||
| 85121de9d7 | |||
| 93b362570d | |||
| 597bd472ea | |||
| e2f01ce740 | |||
| d4982b276c | |||
| c1d93bfd7c | |||
| 469b6b64bc | |||
| c0bdb2407a | |||
| 596431cae7 | |||
| 6b085a58be | |||
| bd514dcce6 | |||
| d83756b4d9 | |||
| 16d989dbfa | |||
| 9517c890b5 | |||
| 8cae1f2ab5 | |||
| 90e7856efb | |||
| 37a14858ad | |||
| 5b5d7e4997 | |||
| 67fef02d71 | |||
| b8c41f54c5 | |||
| 97ea859315 | |||
| 616b1f4a05 | |||
| d1cde64214 | |||
| d061868fdc | |||
| a2cfe9c2a7 | |||
| 8a7c414031 | |||
| 46e0571ed0 | |||
| 1835981f3d | |||
| 87fdb591ce | |||
| 195951a61a | |||
| 1f781eb78a | |||
| 1b63e461cc | |||
| e8dc6b259f | |||
| a7f751f3b3 | |||
| ed18e623db | |||
| b37470b3de | |||
| e246071aac | |||
| 4554c468bc | |||
| 5923edc69a | |||
| 55c24de8c7 | |||
| 25300c1928 | |||
| fc1caf1469 | |||
| 44d33ed96e | |||
| 650b084c72 | |||
| 82c2a202cb | |||
| aaa1f92945 | |||
| 66d7d598fb | |||
| 8d2aecd687 | |||
| 6eff2fe0d1 | |||
| eeb9449c11 | |||
| 94a5a6c4c0 | |||
| a291063b9c | |||
| c17eb00e3b | |||
| 43f37e4776 | |||
| 42cb55d78a | |||
| aaebd01058 | |||
| d7698343ae | |||
| 0b057ccb34 | |||
| 995f3a13d1 | |||
| ab7f4c5ba2 | |||
| be4288fb46 | |||
| 75d8641a38 | |||
| 1d72019645 | |||
| c1c47c5f30 | |||
| fc47af12be | |||
| a9bee998f2 | |||
| 31226e3c75 | |||
| f7aabe8ca9 | |||
| 8ac82b97d3 | |||
| 128af67011 | |||
| fb9a4ec461 | |||
| 2a261cfaf8 | |||
| 224ad46a21 | |||
| 05cc8e2b51 | |||
| ffe3ec0cb4 | |||
| 448dd7ed54 | |||
| 1dc01ef857 | |||
| 0f76e80341 | |||
| 6acfbb7d66 | |||
| fcdc064cac | |||
| 0c92f4a74d | |||
| ac136ec5f6 | |||
| f75f6a8404 | |||
| 415bb4cc88 | |||
| 6a3e1da986 | |||
| 5a6b6c369e | |||
| 66d342880c | |||
| 7fad2b6563 | |||
| 22f50aae45 | |||
| 1daba5db87 | |||
| 83fc22005c | |||
| 7eb7fc2e12 | |||
| 07702afe68 | |||
| 0aa21c007a | |||
| c659e40df7 | |||
| ffacd4d021 | |||
| 54ad6b8dd9 | |||
| 70fc4c0d88 | |||
| 742f570c4c | |||
| 75d67e0e05 | |||
| 7bd7ae41b4 | |||
| 5f9a9b80f0 | |||
| 94208477e9 | |||
| 4da0803f15 | |||
| 72201c296b | |||
| ed2e9b88e7 | |||
| dd88d9254e | |||
| 509f21a9b4 | |||
| b299451cab | |||
| 7e63a18d37 | |||
| b9e718f5b8 | |||
| b4a6f8350b | |||
| 5eb9b95ab5 | |||
| 4e3701ca8d | |||
| 7a0ebbdc53 | |||
| 051c5672b9 | |||
| 57f242ccf8 | |||
| 0c2903f33f | |||
| d7cbebcb02 | |||
| d3f2f987e0 | |||
| 221e6190c8 | |||
| 6a69425688 | |||
| 656fe00302 | |||
| 884c91062d | |||
| a7d9857a69 | |||
| f814f7792c | |||
| e264e10ad6 | |||
| f2d5d62c9c | |||
| af438af8ac | |||
| 041b51a7f8 | |||
| 330d5047e7 | |||
| e476186cbc | |||
| 3124b0f39c | |||
| 55f68a9197 | |||
| c92a2ecbf5 | |||
| d248b30eb3 | |||
| c71009fea9 | |||
| b15aca80ca | |||
| 25e043afea | |||
| 0395c84270 | |||
| e66c46ff59 | |||
| 46f4493f04 | |||
| da5de30d7b | |||
| 5cbcd89369 | |||
| 32f5cc7fba | |||
| c6005ea389 | |||
| 60b6a7cdfc | |||
| f5bc5fa24a | |||
| f9382ed32e | |||
| c0cfd75a2e | |||
| 64fa04306c | |||
| 7a583cb7e6 | |||
| cb0b5f7146 | |||
| 8a3b1ae29d | |||
| 717282b4b7 | |||
| 78a4a167ac | |||
| 23d7ef36d2 | |||
| d1dd6b7a8f | |||
| 9c65fd814b | |||
| 58a7d67922 | |||
| b1fb2982ef | |||
| f206baf3f0 | |||
| 6916c59483 | |||
| 41914d9b7a | |||
| 1f89b94f66 | |||
| 80b0aef210 | |||
| b1214f6c35 | |||
| c7dcf92a2e | |||
| 50ce5aa2b4 | |||
| b3b8e71caa | |||
| 3686cba6b4 | |||
| b1967b42e3 | |||
| bfa0c46588 | |||
| 69ee18e13d | |||
| c180a521ec | |||
| 59f5846d1a | |||
| 7e85524e51 | |||
| 59e1811187 | |||
| 120332924b | |||
| 01ae3334ee | |||
| 03cf8799c4 | |||
| 54c50f6446 | |||
| 09aa5d6350 | |||
| e5ff416c2d | |||
| 21ea527623 | |||
| 36c34e05f8 | |||
| 7a93b9e565 | |||
| 3945dc9f3f | |||
| e96d2fa666 | |||
| 3a2f285a87 | |||
| a09481dea2 | |||
| 03ff495011 | |||
| 657b0089b1 | |||
| 7d74e1d2c4 | |||
| 81ac53ff0a | |||
| 6c999d10c3 | |||
| 1e58941323 | |||
| a52b57cc38 | |||
| bffa51f7df | |||
| d5281d2023 | |||
| 5b8e3b4189 | |||
| 372cf4a8cb | |||
| fc17580d9a | |||
| dfff2a1134 | |||
| b3d54b7620 | |||
| a445b03523 | |||
| 5d37012075 | |||
| a9db538c63 | |||
| 526af26536 | |||
| fac8d53163 | |||
| 0804b5e6c5 | |||
| 464a56ad52 | |||
| 0793fff222 | |||
| 4fa122b827 | |||
| 583b6cc20b | |||
| ed17920bd4 | |||
| 3cc7d54cc1 | |||
| d71d45b958 | |||
| e7c6ff9499 | |||
| 1b496dd472 | |||
| c1781d89df | |||
| 12bfa404c8 | |||
| 76e571ea0a | |||
| 48ee582f37 | |||
| 9d0398f81d | |||
| d2d0e99f9d | |||
| e165b3dae5 | |||
| 6abd8a0ca0 | |||
| 78acfc18fc | |||
| aced8b507c | |||
| fbc33815a3 | |||
| 768d72ec24 | |||
| bd9c0efab7 | |||
| d358dc1182 | |||
| 956d868106 | |||
| 0fcef494a6 | |||
| 6f6fe6ad06 | |||
| 926636c331 | |||
| 2e6a264f98 | |||
| 95ecad8382 | |||
| 035771de81 | |||
| 1a53bc3de5 | |||
| e621eb7455 | |||
| 261583cb92 | |||
| 1bc48d2bea | |||
| 9bab708e6e | |||
| 103e0f3b06 | |||
| c8608db4ee | |||
| 869f18483f | |||
| 32fb90e056 | |||
| f636414fb7 | |||
| a4fd0dc597 | |||
| 2a437536d4 | |||
| a39f42974f | |||
| 2e58982419 | |||
| 72cca0473a | |||
| 02212406c4 | |||
| 2fade4e604 | |||
| 469ba3a391 | |||
| 0b3980e564 | |||
| cfcf7aa2ae | |||
| fc6f242f86 | |||
| ec8dee3588 | |||
| e7fd37efeb | |||
| ccd4665d82 | |||
| fe4791c216 | |||
| 6e46124c94 | |||
| 1275f22599 | |||
| 533a719914 | |||
| a085632b8e | |||
| 1ef5a8e6c5 | |||
| ab5d6dbea1 | |||
| ffd8c59c8e | |||
| 83c3a116f3 | |||
| f695a3f40a | |||
| f41f2bfdab | |||
| 17f7a97ef3 | |||
| 3698c6431c | |||
| 4d88af4601 | |||
| dce869b566 | |||
| 1d641b2432 | |||
| 5a5539da97 | |||
| e12d99ba63 | |||
| 4612cea970 | |||
| da4fa96499 | |||
| 4137266041 | |||
| 9427942ea8 | |||
| 5b8b973345 | |||
| d44dc00757 | |||
| 37655e1e21 | |||
| a1f961db97 | |||
| 62d0e020db | |||
| fa5f379a53 | |||
| 3f6174e8cc | |||
| 1fd949d4ec | |||
| de6fa63d21 | |||
| cfe7bc8155 | |||
| c6c4636b9b | |||
| bd74e07ce1 | |||
| 45c1072291 | |||
| 33787d0685 | |||
| 068d281b19 | |||
| 56344cadeb | |||
| 3c2d541d60 | |||
| 0671d712fa | |||
| 6961089425 | |||
| b6d797fc78 | |||
| 3e5a756016 | |||
| d24cbae39a | |||
| 480113e080 | |||
| 3167426b53 | |||
| 863124efbb | |||
| 80cc0fcc61 | |||
| ddf09a4cf5 | |||
| 012a045c8e | |||
| 145ef8b071 | |||
| 3157bf63a6 | |||
| e202fd988b | |||
| 8155d88db7 | |||
| 6ce3d2916b | |||
| 450bb9040d | |||
| 4f8b882554 | |||
| 8a451bb5f6 | |||
| fe7f23238c | |||
| 936e2fb4e2 | |||
| bb743a4d30 | |||
| 3238c85514 | |||
| e2c0fa8d8a | |||
| 50f946e4a7 | |||
| 556a0d5d84 | |||
| 25c82d80f5 | |||
| 7e47906475 | |||
| 24ac6d2c25 | |||
| 68449a0d21 | |||
| bb9fbb55b6 | |||
| c834f0a372 | |||
| 1414322f71 | |||
| 17f46c291b | |||
| 18594c4886 | |||
| d906738097 | |||
| 43f19f78bb | |||
| 3eacd8b754 | |||
| 3d45956f15 | |||
| fb20ae7e1a | |||
| 5c85c3315d | |||
| d0529e76ba | |||
| 4c49209f71 | |||
| 3668850e8f | |||
| 4525a43e63 | |||
| 077abdb602 | |||
| b6087c0f10 | |||
| 972972a4d9 | |||
| 45a397bd77 | |||
| f54cc79f6b | |||
| 2cad208038 | |||
| f1a4754568 | |||
| d8841911de | |||
| fe054136b1 | |||
| e7a8371cbb | |||
| d82dfc65b7 | |||
| 2de869d9c3 | |||
| 080282a0bc | |||
| 8242c139c2 | |||
| 5b4c5d0f31 | |||
| 9ad10863de | |||
| 14f2522c3e | |||
| 01fc63fc98 | |||
| a57d524273 | |||
| 93bd95436f | |||
| db9aa5d9dc | |||
| 48443e3e09 | |||
| dae60b5a08 | |||
| 013a192485 | |||
| bc37480f0d | |||
| a95b6e0e61 | |||
| ac78e3e2ec | |||
| 77a484e698 | |||
| f1f706dd0d | |||
| a6123cfbe4 | |||
| 07142cab8b | |||
| 9a27bc8627 | |||
| e6cb60b793 | |||
| 706ffb56f7 | |||
| 8cadee28c1 | |||
| ef58020fd4 | |||
| a54fa7c9b1 | |||
| a8d411a77b | |||
| 5f6f5dbfc4 | |||
| aeb4b6b412 | |||
| 9efc4dec18 | |||
| 7b826b696c | |||
| b1c21c405a | |||
| cd1218c78e | |||
| a8c1fd1e4e | |||
| 14d990df7f | |||
| 93e8f9cb36 | |||
| 04d2e769bb | |||
| 5b0d875a42 | |||
| 820f4be02f | |||
| 0ef040e5b6 | |||
| d2bbf2965d | |||
| bf32cf3265 | |||
| 5f0192ee48 | |||
| 91e1ded3bf | |||
| c70f6e3122 | |||
| 56260cd23f | |||
| fdbb9803b5 | |||
| 83abc20300 | |||
| 88cf0b2cdc | |||
| 16950dbc54 | |||
| 43bf9e6c21 | |||
| 2698d9d23a | |||
| 6eb0583eeb | |||
| 49f140e9bc | |||
| 9ddc10431a | |||
| cad1c9eae6 | |||
| a6708594bb | |||
| 14027e2fc6 | |||
| cf519f48e7 | |||
| eb884f7ef7 | |||
| 9902a11621 | |||
| abbec501f7 | |||
| 67629ce0b7 | |||
| 5f024eb1f7 | |||
| db99225c65 | |||
| 6717f2a68d | |||
| 56a7e1e2f0 | |||
| e434b0233a | |||
| 4b33971155 | |||
| 9e71287c25 | |||
| 9784c6c828 | |||
| 732b6a3556 | |||
| dc1e17ba0c | |||
| f05d5973af | |||
| deb48487f3 | |||
| 78f3abc64f | |||
| e45bc3834a | |||
| 0d9db1b6f2 | |||
| ce555aa5e9 | |||
| 07ca82e599 | |||
| a9339589bb | |||
| c8ed650f1c | |||
| cd78d8d3fa | |||
| 7fdc935fb9 | |||
| c8069325b3 | |||
| 9d08e02fe1 | |||
| a11ea598a2 | |||
| 2713b05e8c | |||
| fef5a5ca52 | |||
| 9d339d8b11 | |||
| 4e86aa3f59 | |||
| 221e4b665c | |||
| e67f235a9f | |||
| 741ebbacca | |||
| b63b789f77 | |||
| a63702ef90 | |||
| a4a4550753 | |||
| fd864655f6 | |||
| c1da09507a | |||
| ed2ea220bf | |||
| 7738cbe751 | |||
| bf16ea3607 | |||
| d6f44e069c | |||
| 899cf392f4 | |||
| d99451b45c | |||
| 5b31f8edf6 | |||
| 00235e039b | |||
| 2dfaef4220 | |||
| 13fceacfe4 | |||
| f8dc32b387 | |||
| 828f2f8b92 | |||
| 734399755d | |||
| d8f106b976 | |||
| 9a524dd671 | |||
| 0775296003 | |||
| 390534c14e | |||
| 2a644f64ad | |||
| e0298141cf | |||
| df7119bb22 | |||
| 1d5bba831e | |||
| 0b4be70c00 | |||
| 786737650b | |||
| 54c80a2e1f | |||
| b376211a0e | |||
| 1990a3063e | |||
| 5abf22ad8a | |||
| b7b87d87fc | |||
| 20184424ab | |||
| d5de12b69e | |||
| d1a3350085 | |||
| e0b84c71a7 | |||
| 3bc1d6a690 | |||
| 786c74ef2c | |||
| 3e9b5f5449 | |||
| 5d071488d3 | |||
| 90d234a458 | |||
| 0032bb6aee | |||
| 6e6755d805 | |||
| 132b990f10 | |||
| 34a3d81eff | |||
| 43a4217497 | |||
| e0ec5826ca | |||
| 5413a01360 | |||
| d9c3a29404 | |||
| bcce91476c | |||
| 56f0f454d0 | |||
| 25e63edf77 | |||
| d150851ff5 | |||
| 2e2840c71e | |||
| ff276fcc58 | |||
| 2852fa3c5e | |||
| 1c6d498621 | |||
| 3f0e4bb654 | |||
| a59d78a7c7 | |||
| 0a24202f1e | |||
| cbc86d674d | |||
| 082628771b | |||
| 93b50e7d6e | |||
| c6de4e47d7 | |||
| 0e9e378bdf | |||
| de4b3d6290 | |||
| 56f75aecc7 | |||
| 0fe009d37c | |||
| 49db283e71 | |||
| 7058366623 | |||
| ced45513b8 | |||
| 15e15c9635 | |||
| d53c82eee2 | |||
| e1e0b0cf7d | |||
| 33e013a59f | |||
| 96a74776f8 | |||
| bb63d08682 | |||
| 32655567da | |||
| ff5f5f65e8 | |||
| 1f97aa09fa | |||
| 32e5ebb8a3 | |||
| 597e00dd86 | |||
| dd31191845 | |||
| e9d95b1311 | |||
| 3319547a0e | |||
| 1a00730cdd | |||
| 466723573c | |||
| ea784d47f4 | |||
| 77d5ba2862 | |||
| f4580a1097 | |||
| 9e3d1f0baa | |||
| c002c4b610 | |||
| dde5e910cf | |||
| 5218332bce | |||
| 28cd08bbba | |||
| 3cb0575a1e | |||
| dc1c1b9569 | |||
| 662d117b66 | |||
| b2449757f9 | |||
| a0753bfc88 | |||
| e2a771bdaa | |||
| 23de9df2a5 | |||
| 5c739ebed2 | |||
| d3f8d7120f | |||
| 21fd251edf | |||
| 28cededb90 | |||
| d420719649 | |||
| 0018fbacd3 | |||
| 8c41d2f4cb | |||
| 3941590d0c | |||
| dc4a7c35da | |||
| e8c9b70ae8 | |||
| 74d240dfd4 | |||
| 7d296b2119 | |||
| 373793ce9a | |||
| 5c0ec7554b | |||
| 792fa45dca | |||
| 743aaea15e | |||
| de03ed0aec | |||
| e68ec16a34 | |||
| 68a0219d0f | |||
| 38d9533afd | |||
| 7538af5e09 | |||
| 2e659c1ab0 | |||
| ad0cc5f0be | |||
| 7ae9482e7b | |||
| 7fb95dfabf | |||
| 83cc5d24f2 | |||
| 38b3096c9a | |||
| df8f21e559 | |||
| f4979fcf19 | |||
| 431b7375c1 | |||
| a6627145c8 | |||
| 3045cf1aef | |||
| c65b2944b3 | |||
| 2ae5a81c15 | |||
| ed8b78600e | |||
| 644a03e40e | |||
| 88ce93ab04 | |||
| 8878dc61d3 | |||
| 03d38557e5 | |||
| 37b59bb5b9 | |||
| 19eea68e0f | |||
| ce7aae16c9 | |||
| fd9ba97479 | |||
| 919debdd13 | |||
| 36690de285 | |||
| ca4ead8fd8 | |||
| a81f981471 | |||
| d6fd2b0afa | |||
| 0478ae3da8 | |||
| 9c33f4858f | |||
| f2eaa9052e | |||
| 21d0641110 | |||
| 67d05f99e9 | |||
| 21d6a28715 | |||
| 1149a8d9a4 | |||
| 5e98172afb | |||
| 9b3e94c7c8 | |||
| 30a1b65e94 | |||
| 9bb46ecb88 | |||
| 269e6c4f38 | |||
| 7f65ae3f92 | |||
| ee6b365003 | |||
| 2ad4bd5c0a | |||
| 0958740b51 | |||
| 9cdfd8b75a | |||
| 3c8a0081bc | |||
| 088e0e736a | |||
| cbb0681f95 | |||
| 55c408a8bf | |||
| 07379acf7f | |||
| a1af93f8be | |||
| b9a9da4ec7 | |||
| 05a5b5b675 | |||
| 0fb17eee43 | |||
| a1474e09e5 | |||
| a33c7d7786 | |||
| c08d9762d9 | |||
| d43e6e5736 | |||
| 380786bfde | |||
| ffcf064f83 | |||
| 252718bbaf | |||
| 5725e54334 | |||
| c20856ca17 | |||
| 402afa1e85 | |||
| 5b4e75000b | |||
| 9c73e9cf4e | |||
| b10c3db13d | |||
| 1a052913e9 | |||
| e930a1d0dc | |||
| fe290aa214 | |||
| a2e69bd250 | |||
| d2a35eb8de | |||
| 3437d8b4b0 | |||
| b862bf4284 | |||
| de22a367b1 | |||
| 17ab895652 | |||
| a4d5815e1b | |||
| 4cbfaaa72b | |||
| 92943f08d9 | |||
| 10ef1c7e93 | |||
| 02c762c268 | |||
| bbf0ca92af | |||
| d2dfc6d63b | |||
| a18240fcd7 | |||
| d36e5dccf9 | |||
| 9af1d6f63b | |||
| ab6d46558b | |||
| e94abfc986 | |||
| 5c652c1f79 | |||
| 89aa0f0cc8 | |||
| 085589bcec | |||
| 95d0d6f3e8 | |||
| c62ef4ae81 | |||
| 3df81ca6f0 | |||
| 578326eccd | |||
| 2335ccddaa | |||
| 477e30f542 | |||
| 7bf3d7e10a | |||
| 1bef659b10 | |||
| e3f7bd8ab8 | |||
| 45c731de3c | |||
| 535770abbd | |||
| eccea8eba0 | |||
| ab200a1dfb | |||
| ca122b20c9 | |||
| 74b407ebc7 | |||
| fbf2fe2404 | |||
| b968adffc1 | |||
| c275992f7b | |||
| 4e2c686db1 | |||
| bfc69562d8 | |||
| 9e6a7bf16b | |||
| 890e0e9054 | |||
| cf7e7c44ff | |||
| 0f169f176d | |||
| 429fc921b1 | |||
| e7a9a41a2f | |||
| d1c24f47b2 | |||
| 007676b400 | |||
| c0c235bead | |||
| a3aacb5285 | |||
| 5977c09b05 | |||
| e81d3dad3e | |||
| 5aabaebd96 | |||
| 7b60bca297 | |||
| a07d7456c8 | |||
| f33369bf0c | |||
| 1abcff39c7 | |||
| c1caf84d92 | |||
| 86c069fe64 | |||
| ce0140ef67 | |||
| bba43c5109 | |||
| d99a415502 | |||
| 9049593ff5 | |||
| e74c098b7a | |||
| d06a44378d | |||
| 0a8da376fc | |||
| 2a0f940a42 | |||
| 8aa067795a | |||
| 3cdb81c5ba | |||
| e8259791f0 | |||
| 55af786852 | |||
| 8a916602c4 | |||
| 7101c7987c | |||
| bd48955f39 | |||
| 53adcd9157 | |||
| c5a2bb8914 | |||
| 66e5958283 | |||
| 9db445c3ee | |||
| 574438b51e | |||
| a05885140d | |||
| 8878fac4e7 | |||
| 7ee97a961c | |||
| 737ff62e92 | |||
| 07ada5a1b7 | |||
| 8caeed6b18 | |||
| b5adff5327 | |||
| 3894895d32 | |||
| 7f53c97fb2 | |||
| 44bd4b9511 | |||
| 2a1b5e0154 | |||
| 8c0d48fe0a | |||
| 0863e60d29 | |||
| 451c117ea4 | |||
| 388c8c8bec | |||
| 5904070bb2 | |||
| 35ac87ec10 | |||
| 8f8c2a291b | |||
| 592a2dcede | |||
| a3221475e5 | |||
| 25f5031422 | |||
| 63b94263af | |||
| 217595bb01 | |||
| 2dd8119abe | |||
| 20e0fe3941 | |||
| 0fa97de06e | |||
| 38da13fea3 | |||
| fb9880bff4 | |||
| acc790f590 | |||
| 76c572cf7c | |||
| 0904fea109 | |||
| 6df89e7abf | |||
| 21afda6dc2 | |||
| 74c0ed27ba | |||
| dc680a3385 | |||
| 88e5b22d16 | |||
| 27cd10e072 | |||
| d35f524865 | |||
| ca223fa4df | |||
| 14962eb6cc | |||
| b9f409d6d9 | |||
| a8681ac88f | |||
| c1e6786ea1 | |||
| 1c8d101fc3 | |||
| 7a9140bdcd | |||
| 511f94fc7f | |||
| 548b1ead2f | |||
| 33f67140f2 | |||
| 8787dc23d0 | |||
| e0ae92ccc7 | |||
| bdb86d7119 | |||
| a1a3d316e3 | |||
| 672b86ef88 | |||
| a3c9d5873c | |||
| 0e975757b8 | |||
| 391ee10cb8 | |||
| 4f374c0c01 | |||
| dde303f13a | |||
| 264c678eaa | |||
| 854d94056e | |||
| 9d4c22c706 | |||
| 9b12895fab | |||
| 93478a55d7 | |||
| a76cbf8b70 | |||
| 6597d5bd28 | |||
| fd28f37c0d | |||
| d219f65e7a | |||
| 865f652476 | |||
| 8008918d8b | |||
| 75d0bd01c2 | |||
| 029c6cd182 | |||
| 71f771c22c | |||
| 0993d5ce4a | |||
| 38bd05867d | |||
| 79089d8981 | |||
| 44e51970e1 | |||
| 47bde052ca | |||
| bd6a473d4f | |||
| cd23053007 | |||
| 6e11fd0f2e | |||
| 277b4336d3 | |||
| 1c1f9b6cb8 | |||
| c23df5e1d5 | |||
| c47cef6fbf | |||
| 83b7b3257a | |||
| 270be95e68 | |||
| 1c919b8b88 | |||
| 1e51a2cdd7 | |||
| 7ba44b15a7 | |||
| 4a94f515b3 | |||
| b229b2f40d | |||
| e4f0613fab | |||
| ecff810021 | |||
| fdde97cbbf | |||
| c2a5641e6a | |||
| 5a47c4850d | |||
| 70b8a941bb | |||
| eb01b42425 | |||
| 8708e487ae | |||
| e020b8bf32 | |||
| 8e27121e10 | |||
| 06870b4f64 | |||
| 4cfcc48b23 | |||
| 60c244c31d | |||
| d122bddae2 | |||
| 69e6221906 | |||
| 68eefd083e | |||
| a647917074 | |||
| 099197ba8c | |||
| baa2ed5ecc | |||
| f8ba623fc1 | |||
| 6bcdf36ca6 | |||
| 416d949d80 | |||
| 0b75a0028b | |||
| 0901d7461e | |||
| 61772b75ff | |||
| 0ade57b5a6 | |||
| 61604adf9a | |||
| 8bd147b205 | |||
| 724f53e972 | |||
| c10478ec68 | |||
| cdf12ee03d | |||
| 964a8dbb82 | |||
| 7ad48bfc44 | |||
| da90510b98 | |||
| 4bd1598c2c | |||
| 6aa8d56d9f | |||
| ccf7d794e9 | |||
| 50ed2fb257 | |||
| 5ae030997a | |||
| 52dabcaad9 | |||
| 35e8a0c374 | |||
| be292729a5 | |||
| 1649c478b6 | |||
| 42feb54d80 | |||
| bbd088a957 | |||
| 5417d0a90c | |||
| 417b5d61a4 | |||
| f13aad21cb | |||
| 79e8ee46c0 | |||
| e3eaaeaf17 | |||
| e550216f85 | |||
| 1afb4a7a76 | |||
| 391eb9d469 | |||
| 494f094fa1 | |||
| aa0f5df218 | |||
| 6fc740a98b | |||
| 7da90ff7e4 | |||
| 61b5714652 | |||
| d2df426489 | |||
| e6c75ed173 | |||
| a353c6956e | |||
| a367d8515f | |||
| 2b7a22a29a | |||
| e6712a50d2 | 
| @ -1,9 +1,11 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2021.2.2-stable | current_version = 2021.4.1-rc1 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||||
| serialize = {major}.{minor}.{patch}-{release} | serialize =  | ||||||
|  | 	{major}.{minor}.{patch}-{release} | ||||||
|  | 	{major}.{minor}.{patch} | ||||||
| message = release: {new_version} | message = release: {new_version} | ||||||
| tag_name = version/{new_version} | tag_name = version/{new_version} | ||||||
|  |  | ||||||
| @ -34,3 +36,9 @@ values = | |||||||
| [bumpversion:file:outpost/pkg/version.go] | [bumpversion:file:outpost/pkg/version.go] | ||||||
|  |  | ||||||
| [bumpversion:file:web/src/constants.ts] | [bumpversion:file:web/src/constants.ts] | ||||||
|  |  | ||||||
|  | [bumpversion:file:web/nginx.conf] | ||||||
|  |  | ||||||
|  | [bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md] | ||||||
|  |  | ||||||
|  | [bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md] | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/codecov.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.github/codecov.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | coverage: | ||||||
|  |   precision: 2 | ||||||
|  |   round: up | ||||||
							
								
								
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| version: 2 | version: 2 | ||||||
| updates: | updates: | ||||||
| - package-ecosystem: gomod | - package-ecosystem: gomod | ||||||
|   directory: "/proxy" |   directory: "/outpost" | ||||||
|   schedule: |   schedule: | ||||||
|     interval: daily |     interval: daily | ||||||
|     time: "04:00" |     time: "04:00" | ||||||
| @ -41,7 +41,7 @@ updates: | |||||||
|   assignees: |   assignees: | ||||||
|   - BeryJu |   - BeryJu | ||||||
| - package-ecosystem: docker | - package-ecosystem: docker | ||||||
|   directory: "/proxy" |   directory: "/outpost" | ||||||
|   schedule: |   schedule: | ||||||
|     interval: daily |     interval: daily | ||||||
|     time: "04:00" |     time: "04:00" | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -18,11 +18,11 @@ jobs: | |||||||
|       - name: Building Docker Image |       - name: Building Docker Image | ||||||
|         run: docker build |         run: docker build | ||||||
|           --no-cache |           --no-cache | ||||||
|           -t beryju/authentik:2021.2.2-stable |           -t beryju/authentik:2021.4.1-rc1 | ||||||
|           -t beryju/authentik:latest |           -t beryju/authentik:latest | ||||||
|           -f Dockerfile . |           -f Dockerfile . | ||||||
|       - name: Push Docker Container to Registry (versioned) |       - name: Push Docker Container to Registry (versioned) | ||||||
|         run: docker push beryju/authentik:2021.2.2-stable |         run: docker push beryju/authentik:2021.4.1-rc1 | ||||||
|       - name: Push Docker Container to Registry (latest) |       - name: Push Docker Container to Registry (latest) | ||||||
|         run: docker push beryju/authentik:latest |         run: docker push beryju/authentik:latest | ||||||
|   build-proxy: |   build-proxy: | ||||||
| @ -48,17 +48,20 @@ jobs: | |||||||
|           cd outpost/ |           cd outpost/ | ||||||
|           docker build \ |           docker build \ | ||||||
|           --no-cache \ |           --no-cache \ | ||||||
|           -t beryju/authentik-proxy:2021.2.2-stable \ |           -t beryju/authentik-proxy:2021.4.1-rc1 \ | ||||||
|           -t beryju/authentik-proxy:latest \ |           -t beryju/authentik-proxy:latest \ | ||||||
|           -f proxy.Dockerfile . |           -f proxy.Dockerfile . | ||||||
|       - name: Push Docker Container to Registry (versioned) |       - name: Push Docker Container to Registry (versioned) | ||||||
|         run: docker push beryju/authentik-proxy:2021.2.2-stable |         run: docker push beryju/authentik-proxy:2021.4.1-rc1 | ||||||
|       - name: Push Docker Container to Registry (latest) |       - name: Push Docker Container to Registry (latest) | ||||||
|         run: docker push beryju/authentik-proxy:latest |         run: docker push beryju/authentik-proxy:latest | ||||||
|   build-static: |   build-static: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v1 |       - uses: actions/checkout@v1 | ||||||
|  |       - name: prepare ts api client | ||||||
|  |         run: | | ||||||
|  |           docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 | ||||||
|       - name: Docker Login Registry |       - name: Docker Login Registry | ||||||
|         env: |         env: | ||||||
|           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
| @ -69,11 +72,11 @@ jobs: | |||||||
|           cd web/ |           cd web/ | ||||||
|           docker build \ |           docker build \ | ||||||
|           --no-cache \ |           --no-cache \ | ||||||
|           -t beryju/authentik-static:2021.2.2-stable \ |           -t beryju/authentik-static:2021.4.1-rc1 \ | ||||||
|           -t beryju/authentik-static:latest \ |           -t beryju/authentik-static:latest \ | ||||||
|           -f Dockerfile . |           -f Dockerfile . | ||||||
|       - name: Push Docker Container to Registry (versioned) |       - name: Push Docker Container to Registry (versioned) | ||||||
|         run: docker push beryju/authentik-static:2021.2.2-stable |         run: docker push beryju/authentik-static:2021.4.1-rc1 | ||||||
|       - name: Push Docker Container to Registry (latest) |       - name: Push Docker Container to Registry (latest) | ||||||
|         run: docker push beryju/authentik-static:latest |         run: docker push beryju/authentik-static:latest | ||||||
|   test-release: |   test-release: | ||||||
| @ -107,5 +110,5 @@ jobs: | |||||||
|           SENTRY_PROJECT: authentik |           SENTRY_PROJECT: authentik | ||||||
|           SENTRY_URL: https://sentry.beryju.org |           SENTRY_URL: https://sentry.beryju.org | ||||||
|         with: |         with: | ||||||
|           tagName: 2021.2.2-stable |           tagName: 2021.4.1-rc1 | ||||||
|           environment: beryjuorg-prod |           environment: beryjuorg-prod | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -201,3 +201,4 @@ local.env.yml | |||||||
| selenium_screenshots/ | selenium_screenshots/ | ||||||
| backups/ | backups/ | ||||||
| media/ | media/ | ||||||
|  | *mmdb | ||||||
|  | |||||||
| @ -1,12 +0,0 @@ | |||||||
| strictness: medium |  | ||||||
| test-warnings: true |  | ||||||
| doc-warnings: false |  | ||||||
|  |  | ||||||
| ignore-paths: |  | ||||||
|   - migrations |  | ||||||
|   - docs |  | ||||||
|   - node_modules |  | ||||||
|  |  | ||||||
| uses: |  | ||||||
|   - django |  | ||||||
|   - celery |  | ||||||
							
								
								
									
										29
									
								
								.pylintrc
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.pylintrc
									
									
									
									
									
								
							| @ -1,29 +0,0 @@ | |||||||
| [MASTER] |  | ||||||
|  |  | ||||||
| disable = |  | ||||||
|     arguments-differ, |  | ||||||
|     no-self-use, |  | ||||||
|     fixme, |  | ||||||
|     locally-disabled, |  | ||||||
|     too-many-ancestors, |  | ||||||
|     too-few-public-methods, |  | ||||||
|     import-outside-toplevel, |  | ||||||
|     bad-continuation, |  | ||||||
|     signature-differs, |  | ||||||
|     similarities, |  | ||||||
|     cyclic-import, |  | ||||||
|     protected-access, |  | ||||||
|     unsubscriptable-object # remove when pylint is upgraded to 2.6 |  | ||||||
|  |  | ||||||
| load-plugins=pylint_django,pylint.extensions.bad_builtin |  | ||||||
|  |  | ||||||
| extension-pkg-whitelist=lxml,xmlsec |  | ||||||
|  |  | ||||||
| # Allow constants to be shorter than normal (and lowercase, for settings.py) |  | ||||||
| const-rgx=[a-zA-Z0-9_]{1,40}$ |  | ||||||
|  |  | ||||||
| ignored-modules=django-otp |  | ||||||
| generated-members=xmlsec.constants.*,xmlsec.tree.*,xmlsec.template.* |  | ||||||
| ignore=migrations |  | ||||||
| max-attributes=12 |  | ||||||
| max-branches=20 |  | ||||||
| @ -15,12 +15,15 @@ WORKDIR / | |||||||
| COPY --from=locker /app/requirements.txt / | COPY --from=locker /app/requirements.txt / | ||||||
| COPY --from=locker /app/requirements-dev.txt / | COPY --from=locker /app/requirements-dev.txt / | ||||||
|  |  | ||||||
|  | ARG GIT_BUILD_HASH | ||||||
|  | ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||||
|  |  | ||||||
| RUN apt-get update && \ | RUN apt-get update && \ | ||||||
|     apt-get install -y --no-install-recommends curl ca-certificates gnupg && \ |     apt-get install -y --no-install-recommends curl ca-certificates gnupg && \ | ||||||
|     curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ |     curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ | ||||||
|     echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ |     echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ | ||||||
|     apt-get update && \ |     apt-get update && \ | ||||||
|     apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config && \ |     apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config libmaxminddb0 && \ | ||||||
|     apt-get clean && \ |     apt-get clean && \ | ||||||
|     pip install -r /requirements.txt --no-cache-dir && \ |     pip install -r /requirements.txt --no-cache-dir && \ | ||||||
|     apt-get remove --purge -y build-essential && \ |     apt-get remove --purge -y build-essential && \ | ||||||
| @ -37,7 +40,7 @@ RUN apt-get update && \ | |||||||
|     chown authentik:authentik /backups |     chown authentik:authentik /backups | ||||||
|  |  | ||||||
| COPY ./authentik/ /authentik | COPY ./authentik/ /authentik | ||||||
| COPY ./pytest.ini / | COPY ./pyproject.toml / | ||||||
| COPY ./xml /xml | COPY ./xml /xml | ||||||
| COPY ./manage.py / | COPY ./manage.py / | ||||||
| COPY ./lifecycle/ /lifecycle | COPY ./lifecycle/ /lifecycle | ||||||
| @ -45,4 +48,5 @@ COPY ./lifecycle/ /lifecycle | |||||||
| USER authentik | USER authentik | ||||||
| STOPSIGNAL SIGINT | STOPSIGNAL SIGINT | ||||||
| ENV TMPDIR /dev/shm/ | ENV TMPDIR /dev/shm/ | ||||||
|  | ENV PYTHONUBUFFERED 1 | ||||||
| ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] | ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,32 +1,26 @@ | |||||||
| all: lint-fix lint coverage gen | all: lint-fix lint coverage gen | ||||||
|  |  | ||||||
| test-full: |  | ||||||
| 	coverage run manage.py test --failfast -v 3 . |  | ||||||
| 	coverage html |  | ||||||
| 	coverage report |  | ||||||
|  |  | ||||||
| test-integration: | test-integration: | ||||||
| 	k3d cluster create || exit 0 | 	k3d cluster create || exit 0 | ||||||
| 	k3d kubeconfig write -o ~/.kube/config --overwrite | 	k3d kubeconfig write -o ~/.kube/config --overwrite | ||||||
| 	coverage run manage.py test --failfast -v 3 tests/integration | 	coverage run manage.py test -v 3 tests/integration | ||||||
|  |  | ||||||
| test-e2e: | test-e2e: | ||||||
| 	coverage run manage.py test --failfast -v 3 tests/e2e | 	coverage run manage.py test --failfast -v 3 tests/e2e | ||||||
|  |  | ||||||
| coverage: | coverage: | ||||||
| 	coverage run manage.py test --failfast -v 3 authentik | 	coverage run manage.py test -v 3 authentik | ||||||
| 	coverage html | 	coverage html | ||||||
| 	coverage report | 	coverage report | ||||||
|  |  | ||||||
| lint-fix: | lint-fix: | ||||||
| 	isort -rc authentik tests lifecycle | 	isort authentik tests lifecycle | ||||||
| 	black authentik tests lifecycle | 	black authentik tests lifecycle | ||||||
|  |  | ||||||
| lint: | lint: | ||||||
| 	pyright authentik tests lifecycle | 	pyright authentik tests lifecycle | ||||||
| 	bandit -r authentik tests lifecycle -x node_modules | 	bandit -r authentik tests lifecycle -x node_modules | ||||||
| 	pylint authentik tests lifecycle | 	pylint authentik tests lifecycle | ||||||
| 	prospector |  | ||||||
|  |  | ||||||
| gen: coverage | gen: coverage | ||||||
| 	./manage.py generate_swagger -o swagger.yaml -f yaml | 	./manage.py generate_swagger -o swagger.yaml -f yaml | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							| @ -6,59 +6,56 @@ verify_ssl = true | |||||||
| [packages] | [packages] | ||||||
| boto3 = "*" | boto3 = "*" | ||||||
| celery = "*" | celery = "*" | ||||||
|  | channels = "*" | ||||||
|  | channels-redis = "*" | ||||||
|  | dacite = "*" | ||||||
| defusedxml = "*" | defusedxml = "*" | ||||||
| django = "*" | django = "*" | ||||||
| django-cors-middleware = "*" |  | ||||||
| django-dbbackup = "*" | django-dbbackup = "*" | ||||||
| django-filter = "*" | django-filter = "*" | ||||||
| django-guardian = "*" | django-guardian = "*" | ||||||
| django-model-utils = "*" | django-model-utils = "*" | ||||||
| django-otp = "*" | django-otp = "*" | ||||||
| django-prometheus = "*" | django-prometheus = "*" | ||||||
| django-recaptcha = "*" |  | ||||||
| django-redis = "*" | django-redis = "*" | ||||||
| djangorestframework = "*" |  | ||||||
| django-storages = "*" | django-storages = "*" | ||||||
|  | djangorestframework = "*" | ||||||
| djangorestframework-guardian = "*" | djangorestframework-guardian = "*" | ||||||
| drf_yasg2 = "*" | docker = "*" | ||||||
|  | drf_yasg = "*" | ||||||
| facebook-sdk = "*" | facebook-sdk = "*" | ||||||
|  | geoip2 = "*" | ||||||
|  | gunicorn = "*" | ||||||
|  | kubernetes = "*" | ||||||
| ldap3 = "*" | ldap3 = "*" | ||||||
| lxml = "*" | lxml = ">=4.6.3" | ||||||
| packaging = "*" | packaging = "*" | ||||||
| psycopg2-binary = "*" | psycopg2-binary = "*" | ||||||
| pycryptodome = "*" | pycryptodome = "*" | ||||||
| pyjwkest = "*" | pyjwkest = "*" | ||||||
| uvicorn = {extras = ["standard"],version = "*"} |  | ||||||
| gunicorn = "*" |  | ||||||
| pyyaml = "*" | pyyaml = "*" | ||||||
| qrcode = "*" |  | ||||||
| requests-oauthlib = "*" | requests-oauthlib = "*" | ||||||
| sentry-sdk = "*" | sentry-sdk = "*" | ||||||
| service_identity = "*" | service_identity = "*" | ||||||
| structlog = "*" | structlog = "*" | ||||||
| swagger-spec-validator = "*" | swagger-spec-validator = "*" | ||||||
|  | twisted = "==20.3.0" | ||||||
| urllib3 = {extras = ["secure"],version = "*"} | urllib3 = {extras = ["secure"],version = "*"} | ||||||
| dacite = "*" | uvicorn = {extras = ["standard"],version = "*"} | ||||||
| channels = "*" | webauthn = "*" | ||||||
| channels-redis = "*" |  | ||||||
| kubernetes = "*" |  | ||||||
| docker = "*" |  | ||||||
| xmlsec = "*" | xmlsec = "*" | ||||||
|  |  | ||||||
| [requires] | [requires] | ||||||
| python_version = "3.9" | python_version = "3.9" | ||||||
|  |  | ||||||
| [dev-packages] | [dev-packages] | ||||||
| autopep8 = "*" |  | ||||||
| bandit = "*" | bandit = "*" | ||||||
| black = "==20.8b1" | black = "==20.8b1" | ||||||
| bumpversion = "*" | bump2version = "*" | ||||||
| colorama = "*" | colorama = "*" | ||||||
| coverage = "*" | coverage = "*" | ||||||
| django-debug-toolbar = "*" |  | ||||||
| pylint = "*" | pylint = "*" | ||||||
| pylint-django = "*" | pylint-django = "*" | ||||||
| selenium = "*" |  | ||||||
| prospector = "*" |  | ||||||
| pytest = "*" | pytest = "*" | ||||||
| pytest-django = "*" | pytest-django = "*" | ||||||
|  | selenium = "*" | ||||||
|  | |||||||
							
								
								
									
										1232
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1232
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @ -1,7 +1,10 @@ | |||||||
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo"> | <p align="center"> | ||||||
|  |     <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo"> | ||||||
|  | </p> | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
|  | [](https://discord.gg/KPnmtNWy) | ||||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||||
| [](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://codecov.io/gh/BeryJu/authentik) | ||||||
| @ -21,8 +24,10 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum | |||||||
|  |  | ||||||
| ## Screenshots | ## Screenshots | ||||||
|  |  | ||||||
|  | Light | Dark | ||||||
|  | --- | --- | ||||||
|  |  |  | ||||||
|  |  |  | ||||||
|  |  | ||||||
| ## Development | ## Development | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,9 +4,8 @@ | |||||||
|  |  | ||||||
| | Version    | Supported          | | | Version    | Supported          | | ||||||
| | ---------- | ------------------ | | | ---------- | ------------------ | | ||||||
| | 0.13.x     | :white_check_mark: | | | 2021.3.x   | :white_check_mark: | | ||||||
| | 0.14.x     | :white_check_mark: | | | 2021.4.x   | :white_check_mark: | | ||||||
| | 2021.1.x   | :white_check_mark: | |  | ||||||
|  |  | ||||||
| ## Reporting a Vulnerability | ## Reporting a Vulnerability | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,2 +1,3 @@ | |||||||
| """authentik""" | """authentik""" | ||||||
| __version__ = "2021.2.2-stable" | __version__ = "2021.4.1-rc1" | ||||||
|  | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								authentik/admin/api/meta.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								authentik/admin/api/meta.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | """Meta API""" | ||||||
|  | from drf_yasg.utils import swagger_auto_schema | ||||||
|  | from rest_framework.fields import CharField | ||||||
|  | from rest_framework.permissions import IsAdminUser | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from rest_framework.response import Response | ||||||
|  | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
|  | from authentik.core.api.utils import PassiveSerializer | ||||||
|  | from authentik.lib.utils.reflection import get_apps | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AppSerializer(PassiveSerializer): | ||||||
|  |     """Serialize Application info""" | ||||||
|  |  | ||||||
|  |     name = CharField() | ||||||
|  |     label = CharField() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AppsViewSet(ViewSet): | ||||||
|  |     """Read-only view set list all installed apps""" | ||||||
|  |  | ||||||
|  |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema(responses={200: AppSerializer(many=True)}) | ||||||
|  |     def list(self, request: Request) -> Response: | ||||||
|  |         """List current messages and pass into Serializer""" | ||||||
|  |         data = [] | ||||||
|  |         for app in sorted(get_apps(), key=lambda app: app.name): | ||||||
|  |             data.append({"name": app.name, "label": app.verbose_name}) | ||||||
|  |         return Response(AppSerializer(data, many=True).data) | ||||||
| @ -2,24 +2,23 @@ | |||||||
| import time | import time | ||||||
| from collections import Counter | from collections import Counter | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
| from typing import Dict, List |  | ||||||
|  |  | ||||||
| from django.db.models import Count, ExpressionWrapper, F, Model | from django.db.models import Count, ExpressionWrapper, F | ||||||
| from django.db.models.fields import DurationField | from django.db.models.fields import DurationField | ||||||
| from django.db.models.functions import ExtractHour | from django.db.models.functions import ExtractHour | ||||||
| from django.utils.timezone import now | from django.utils.timezone import now | ||||||
| from drf_yasg2.utils import swagger_auto_schema | from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method | ||||||
| from rest_framework.fields import SerializerMethodField | from rest_framework.fields import IntegerField, SerializerMethodField | ||||||
| from rest_framework.permissions import IsAdminUser | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import Serializer |  | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
|  | from authentik.core.api.utils import PassiveSerializer | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]: | def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]: | ||||||
|     """Get event count by hour in the last day, fill with zeros""" |     """Get event count by hour in the last day, fill with zeros""" | ||||||
|     date_from = now() - timedelta(days=1) |     date_from = now() - timedelta(days=1) | ||||||
|     result = ( |     result = ( | ||||||
| @ -32,47 +31,51 @@ def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]: | |||||||
|         .annotate(count=Count("pk")) |         .annotate(count=Count("pk")) | ||||||
|         .order_by("age_hours") |         .order_by("age_hours") | ||||||
|     ) |     ) | ||||||
|     data = Counter({d["age_hours"]: d["count"] for d in result}) |     data = Counter({int(d["age_hours"]): d["count"] for d in result}) | ||||||
|     results = [] |     results = [] | ||||||
|     _now = now() |     _now = now() | ||||||
|     for hour in range(0, -24, -1): |     for hour in range(0, -24, -1): | ||||||
|         results.append( |         results.append( | ||||||
|             { |             { | ||||||
|                 "x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000, |                 "x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) | ||||||
|                 "y": data[hour * -1], |                 * 1000, | ||||||
|  |                 "y_cord": data[hour * -1], | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     return results |     return results | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdministrationMetricsSerializer(Serializer): | class CoordinateSerializer(PassiveSerializer): | ||||||
|  |     """Coordinates for diagrams""" | ||||||
|  |  | ||||||
|  |     x_cord = IntegerField(read_only=True) | ||||||
|  |     y_cord = IntegerField(read_only=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LoginMetricsSerializer(PassiveSerializer): | ||||||
|     """Login Metrics per 1h""" |     """Login Metrics per 1h""" | ||||||
|  |  | ||||||
|     logins_per_1h = SerializerMethodField() |     logins_per_1h = SerializerMethodField() | ||||||
|     logins_failed_per_1h = SerializerMethodField() |     logins_failed_per_1h = SerializerMethodField() | ||||||
|  |  | ||||||
|  |     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||||
|     def get_logins_per_1h(self, _): |     def get_logins_per_1h(self, _): | ||||||
|         """Get successful logins per hour for the last 24 hours""" |         """Get successful logins per hour for the last 24 hours""" | ||||||
|         return get_events_per_1h(action=EventAction.LOGIN) |         return get_events_per_1h(action=EventAction.LOGIN) | ||||||
|  |  | ||||||
|  |     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||||
|     def get_logins_failed_per_1h(self, _): |     def get_logins_failed_per_1h(self, _): | ||||||
|         """Get failed logins per hour for the last 24 hours""" |         """Get failed logins per hour for the last 24 hours""" | ||||||
|         return get_events_per_1h(action=EventAction.LOGIN_FAILED) |         return get_events_per_1h(action=EventAction.LOGIN_FAILED) | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdministrationMetricsViewSet(ViewSet): | class AdministrationMetricsViewSet(ViewSet): | ||||||
|     """Login Metrics per 1h""" |     """Login Metrics per 1h""" | ||||||
|  |  | ||||||
|     permission_classes = [IsAdminUser] |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)}) |     @swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """Login Metrics per 1h""" |         """Login Metrics per 1h""" | ||||||
|         serializer = AdministrationMetricsSerializer(True) |         serializer = LoginMetricsSerializer(True) | ||||||
|         return Response(serializer.data) |         return Response(serializer.data) | ||||||
|  | |||||||
| @ -2,48 +2,63 @@ | |||||||
| from importlib import import_module | from importlib import import_module | ||||||
|  |  | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.db.models import Model |  | ||||||
| from django.http.response import Http404 | from django.http.response import Http404 | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from drf_yasg2.utils import swagger_auto_schema | from drf_yasg.utils import swagger_auto_schema | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField | from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField | ||||||
| from rest_framework.permissions import IsAdminUser | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import Serializer |  | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
| from authentik.events.monitored_tasks import TaskInfo | from authentik.core.api.utils import PassiveSerializer | ||||||
|  | from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskSerializer(Serializer): | class TaskSerializer(PassiveSerializer): | ||||||
|     """Serialize TaskInfo and TaskResult""" |     """Serialize TaskInfo and TaskResult""" | ||||||
|  |  | ||||||
|     task_name = CharField() |     task_name = CharField() | ||||||
|     task_description = CharField() |     task_description = CharField() | ||||||
|     task_finish_timestamp = DateTimeField(source="finish_timestamp") |     task_finish_timestamp = DateTimeField(source="finish_timestamp") | ||||||
|  |  | ||||||
|     status = IntegerField(source="result.status.value") |     status = ChoiceField( | ||||||
|  |         source="result.status.name", | ||||||
|  |         choices=[(x.name, x.name) for x in TaskResultStatus], | ||||||
|  |     ) | ||||||
|     messages = ListField(source="result.messages") |     messages = ListField(source="result.messages") | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskViewSet(ViewSet): | class TaskViewSet(ViewSet): | ||||||
|     """Read-only view set that returns all background tasks""" |     """Read-only view set that returns all background tasks""" | ||||||
|  |  | ||||||
|     permission_classes = [IsAdminUser] |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         responses={200: TaskSerializer(many=False), 404: "Task not found"} | ||||||
|  |     ) | ||||||
|  |     # pylint: disable=invalid-name | ||||||
|  |     def retrieve(self, request: Request, pk=None) -> Response: | ||||||
|  |         """Get a single system task""" | ||||||
|  |         task = TaskInfo.by_name(pk) | ||||||
|  |         if not task: | ||||||
|  |             raise Http404 | ||||||
|  |         return Response(TaskSerializer(task, many=False).data) | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: TaskSerializer(many=True)}) |     @swagger_auto_schema(responses={200: TaskSerializer(many=True)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """List current messages and pass into Serializer""" |         """List system tasks""" | ||||||
|         return Response(TaskSerializer(TaskInfo.all().values(), many=True).data) |         tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) | ||||||
|  |         return Response(TaskSerializer(tasks, many=True).data) | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         responses={ | ||||||
|  |             204: "Task retried successfully", | ||||||
|  |             404: "Task not found", | ||||||
|  |             500: "Failed to retry task", | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|     @action(detail=True, methods=["post"]) |     @action(detail=True, methods=["post"]) | ||||||
|     # pylint: disable=invalid-name |     # pylint: disable=invalid-name | ||||||
|     def retry(self, request: Request, pk=None) -> Response: |     def retry(self, request: Request, pk=None) -> Response: | ||||||
| @ -62,12 +77,8 @@ class TaskViewSet(ViewSet): | |||||||
|                     % {"name": task.task_name} |                     % {"name": task.task_name} | ||||||
|                 ), |                 ), | ||||||
|             ) |             ) | ||||||
|             return Response( |             return Response(status=204) | ||||||
|                 { |  | ||||||
|                     "successful": True, |  | ||||||
|                 } |  | ||||||
|             ) |  | ||||||
|         except ImportError:  # pragma: no cover |         except ImportError:  # pragma: no cover | ||||||
|             # if we get an import error, the module path has probably changed |             # if we get an import error, the module path has probably changed | ||||||
|             task.delete() |             task.delete() | ||||||
|             return Response({"successful": False}) |             return Response(status=500) | ||||||
|  | |||||||
| @ -1,27 +1,33 @@ | |||||||
| """authentik administration overview""" | """authentik administration overview""" | ||||||
|  | from os import environ | ||||||
|  |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.db.models import Model | from drf_yasg.utils import swagger_auto_schema | ||||||
| from drf_yasg2.utils import swagger_auto_schema |  | ||||||
| from packaging.version import parse | from packaging.version import parse | ||||||
| from rest_framework.fields import SerializerMethodField | from rest_framework.fields import SerializerMethodField | ||||||
| from rest_framework.mixins import ListModelMixin | from rest_framework.mixins import ListModelMixin | ||||||
| from rest_framework.permissions import IsAdminUser | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import Serializer |  | ||||||
| from rest_framework.viewsets import GenericViewSet | from rest_framework.viewsets import GenericViewSet | ||||||
|  |  | ||||||
| from authentik import __version__ | from authentik import ENV_GIT_HASH_KEY, __version__ | ||||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||||
|  | from authentik.core.api.utils import PassiveSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionSerializer(Serializer): | class VersionSerializer(PassiveSerializer): | ||||||
|     """Get running and latest version.""" |     """Get running and latest version.""" | ||||||
|  |  | ||||||
|     version_current = SerializerMethodField() |     version_current = SerializerMethodField() | ||||||
|     version_latest = SerializerMethodField() |     version_latest = SerializerMethodField() | ||||||
|  |     build_hash = SerializerMethodField() | ||||||
|     outdated = SerializerMethodField() |     outdated = SerializerMethodField() | ||||||
|  |  | ||||||
|  |     def get_build_hash(self, _) -> str: | ||||||
|  |         """Get build hash, if version is not latest or released""" | ||||||
|  |         return environ.get(ENV_GIT_HASH_KEY, "") | ||||||
|  |  | ||||||
|     def get_version_current(self, _) -> str: |     def get_version_current(self, _) -> str: | ||||||
|         """Get current version""" |         """Get current version""" | ||||||
|         return __version__ |         return __version__ | ||||||
| @ -40,12 +46,6 @@ class VersionSerializer(Serializer): | |||||||
|             self.get_version_latest(instance) |             self.get_version_latest(instance) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionViewSet(ListModelMixin, GenericViewSet): | class VersionViewSet(ListModelMixin, GenericViewSet): | ||||||
|     """Get running and latest version.""" |     """Get running and latest version.""" | ||||||
| @ -55,7 +55,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet): | |||||||
|     def get_queryset(self):  # pragma: no cover |     def get_queryset(self):  # pragma: no cover | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=True)}) |     @swagger_auto_schema(responses={200: VersionSerializer(many=False)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """Get running and latest version.""" |         """Get running and latest version.""" | ||||||
|         return Response(VersionSerializer(True).data) |         return Response(VersionSerializer(True).data) | ||||||
|  | |||||||
| @ -7,5 +7,4 @@ class AuthentikAdminConfig(AppConfig): | |||||||
|  |  | ||||||
|     name = "authentik.admin" |     name = "authentik.admin" | ||||||
|     label = "authentik_admin" |     label = "authentik_admin" | ||||||
|     mountpoint = "administration/" |  | ||||||
|     verbose_name = "authentik Admin" |     verbose_name = "authentik Admin" | ||||||
|  | |||||||
| @ -1,107 +0,0 @@ | |||||||
| """Additional fields""" |  | ||||||
| import yaml |  | ||||||
| from django import forms |  | ||||||
| from django.utils.datastructures import MultiValueDict |  | ||||||
| from django.utils.translation import gettext_lazy as _ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArrayFieldSelectMultiple(forms.SelectMultiple): |  | ||||||
|     """This is a Form Widget for use with a Postgres ArrayField. It implements |  | ||||||
|     a multi-select interface that can be given a set of `choices`. |  | ||||||
|     You can provide a `delimiter` keyword argument to specify the delimeter used. |  | ||||||
|  |  | ||||||
|     https://gist.github.com/stephane/00e73c0002de52b1c601""" |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |  | ||||||
|         # Accept a `delimiter` argument, and grab it (defaulting to a comma) |  | ||||||
|         self.delimiter = kwargs.pop("delimiter", ",") |  | ||||||
|         super().__init__(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def value_from_datadict(self, data, files, name): |  | ||||||
|         if isinstance(data, MultiValueDict): |  | ||||||
|             # Normally, we'd want a list here, which is what we get from the |  | ||||||
|             # SelectMultiple superclass, but the SimpleArrayField expects to |  | ||||||
|             # get a delimited string, so we're doing a little extra work. |  | ||||||
|             return self.delimiter.join(data.getlist(name)) |  | ||||||
|  |  | ||||||
|         return data.get(name) |  | ||||||
|  |  | ||||||
|     def get_context(self, name, value, attrs): |  | ||||||
|         return super().get_context(name, value.split(self.delimiter), attrs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CodeMirrorWidget(forms.Textarea): |  | ||||||
|     """Custom Textarea-based Widget that triggers a CodeMirror editor""" |  | ||||||
|  |  | ||||||
|     # CodeMirror mode to enable |  | ||||||
|     mode: str |  | ||||||
|  |  | ||||||
|     template_name = "fields/codemirror.html" |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, mode="yaml", **kwargs): |  | ||||||
|         super().__init__(*args, **kwargs) |  | ||||||
|         self.mode = mode |  | ||||||
|  |  | ||||||
|     def render(self, *args, **kwargs): |  | ||||||
|         attrs = kwargs.setdefault("attrs", {}) |  | ||||||
|         attrs["mode"] = self.mode |  | ||||||
|         return super().render(*args, **kwargs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvalidYAMLInput(str): |  | ||||||
|     """Invalid YAML String type""" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class YAMLString(str): |  | ||||||
|     """YAML String type""" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class YAMLField(forms.JSONField): |  | ||||||
|     """Django's JSON Field converted to YAML""" |  | ||||||
|  |  | ||||||
|     default_error_messages = { |  | ||||||
|         "invalid": _("'%(value)s' value must be valid YAML."), |  | ||||||
|     } |  | ||||||
|     widget = forms.Textarea |  | ||||||
|  |  | ||||||
|     def to_python(self, value): |  | ||||||
|         if self.disabled: |  | ||||||
|             return value |  | ||||||
|         if value in self.empty_values: |  | ||||||
|             return None |  | ||||||
|         if isinstance(value, (list, dict, int, float, YAMLString)): |  | ||||||
|             return value |  | ||||||
|         try: |  | ||||||
|             converted = yaml.safe_load(value) |  | ||||||
|         except yaml.YAMLError: |  | ||||||
|             raise forms.ValidationError( |  | ||||||
|                 self.error_messages["invalid"], |  | ||||||
|                 code="invalid", |  | ||||||
|                 params={"value": value}, |  | ||||||
|             ) |  | ||||||
|         if isinstance(converted, str): |  | ||||||
|             return YAMLString(converted) |  | ||||||
|         if converted is None: |  | ||||||
|             return {} |  | ||||||
|         return converted |  | ||||||
|  |  | ||||||
|     def bound_data(self, data, initial): |  | ||||||
|         if self.disabled: |  | ||||||
|             return initial |  | ||||||
|         try: |  | ||||||
|             return yaml.safe_load(data) |  | ||||||
|         except yaml.YAMLError: |  | ||||||
|             return InvalidYAMLInput(data) |  | ||||||
|  |  | ||||||
|     def prepare_value(self, value): |  | ||||||
|         if isinstance(value, InvalidYAMLInput): |  | ||||||
|             return value |  | ||||||
|         return yaml.dump(value, explicit_start=True, default_flow_style=False) |  | ||||||
|  |  | ||||||
|     def has_changed(self, initial, data): |  | ||||||
|         if super().has_changed(initial, data): |  | ||||||
|             return True |  | ||||||
|         # For purposes of seeing whether something has changed, True isn't the |  | ||||||
|         # same as 1 and the order of keys doesn't matter. |  | ||||||
|         data = self.to_python(data) |  | ||||||
|         return yaml.dump(initial, sort_keys=True) != yaml.dump(data, sort_keys=True) |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| """Forms for modals on overview page""" |  | ||||||
| from django import forms |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyCacheClearForm(forms.Form): |  | ||||||
|     """Form to clear Policy cache""" |  | ||||||
|  |  | ||||||
|     title = "Clear Policy cache" |  | ||||||
|     body = """Are you sure you want to clear the policy cache? |  | ||||||
|     This will cause all policies to be re-evaluated on their next usage.""" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowCacheClearForm(forms.Form): |  | ||||||
|     """Form to clear Flow cache""" |  | ||||||
|  |  | ||||||
|     title = "Clear Flow cache" |  | ||||||
|     body = """Are you sure you want to clear the flow cache? |  | ||||||
|     This will cause all flows to be re-evaluated on their next usage.""" |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| """authentik administration forms""" |  | ||||||
| from django import forms |  | ||||||
|  |  | ||||||
| from authentik.admin.fields import CodeMirrorWidget, YAMLField |  | ||||||
| from authentik.core.models import User |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyTestForm(forms.Form): |  | ||||||
|     """Form to test policies against user""" |  | ||||||
|  |  | ||||||
|     user = forms.ModelChoiceField(queryset=User.objects.all()) |  | ||||||
|     context = YAMLField(widget=CodeMirrorWidget(), required=False, initial=dict) |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| """authentik administrative user forms""" |  | ||||||
|  |  | ||||||
| from django import forms |  | ||||||
|  |  | ||||||
| from authentik.admin.fields import CodeMirrorWidget, YAMLField |  | ||||||
| from authentik.core.models import User |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserForm(forms.ModelForm): |  | ||||||
|     """Update User Details""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = User |  | ||||||
|         fields = ["username", "name", "email", "is_active", "attributes"] |  | ||||||
|         widgets = { |  | ||||||
|             "name": forms.TextInput, |  | ||||||
|             "attributes": CodeMirrorWidget, |  | ||||||
|         } |  | ||||||
|         field_classes = { |  | ||||||
|             "attributes": YAMLField, |  | ||||||
|         } |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| """authentik admin mixins""" |  | ||||||
| from django.contrib.auth.mixins import UserPassesTestMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdminRequiredMixin(UserPassesTestMixin): |  | ||||||
|     """Make sure user is administrator""" |  | ||||||
|  |  | ||||||
|     def test_func(self): |  | ||||||
|         return self.request.user.is_superuser |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| {% load static %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,14 +0,0 @@ | |||||||
| {% extends base_template|default:"generic/form.html" %} |  | ||||||
|  |  | ||||||
| {% load authentik_utils %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1> |  | ||||||
|     {% trans 'Generate Certificate-Key Pair' %} |  | ||||||
| </h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% trans 'Generate Certificate-Key Pair' %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,122 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-key"></i> |  | ||||||
|             {% trans 'Certificate-Key Pairs' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Import certificates of external providers or create certificates to sign requests with." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-generate' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Generate' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Private Key available' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Fingerprint' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for kp in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div> |  | ||||||
|                             <div>{{ kp.name }}</div> |  | ||||||
|                         </div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {% if kp.key_data is not None %} |  | ||||||
|                             {% trans 'Yes' %} |  | ||||||
|                             {% else %} |  | ||||||
|                             {% trans 'No' %} |  | ||||||
|                             {% endif %} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <code>{{ kp.fingerprint }}</code> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-update' pk=kp.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-delete' pk=kp.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-key pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Certificates.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any certificates." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no certificates exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| {% extends base_template|default:"generic/form.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1> |  | ||||||
| {% trans 'Import Flow' %} |  | ||||||
| </h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% trans 'Import Flow' %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,135 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-process-automation"></i> |  | ||||||
|             {% trans 'Flows' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:flow-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:flow-import' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                             {% trans 'Import' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Designation' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Stages' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Policies' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for flow in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <a href="/flows/{{ flow.slug }}"> |  | ||||||
|                             <div><code>{{ flow.slug }}</code></div> |  | ||||||
|                             <small>{{ flow.name }}</small> |  | ||||||
|                         </a> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ flow.designation }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ flow.stages.all|length }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ flow.policies.all|length }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:flow-update' pk=flow.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:flow-delete' pk=flow.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a> |  | ||||||
|                         <a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-process-automation pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Flows.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any flows." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no flows exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:flow-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:flow-import' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                         {% trans 'Import' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,114 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-users"></i> |  | ||||||
|             {% trans 'Groups' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Group users together and give them permissions based on the membership." %} |  | ||||||
|         </p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:group-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Parent' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Members' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for group in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ group.name }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ group.parent }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ group.users.all|length }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:group-update' pk=group.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:group-delete' pk=group.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-users pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Groups.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any groups." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no group exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:group-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,153 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load humanize %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon-integration"></i> |  | ||||||
|             {% trans 'Outpost Service-Connections' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Outpost Service-Connections define how authentik connects to external platforms to manage and deploy Outposts." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                         </button> |  | ||||||
|                         <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                             {% for type, name in types.items %} |  | ||||||
|                             <li> |  | ||||||
|                                 <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}"> |  | ||||||
|                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                         {{ name|verbose_name }}<br> |  | ||||||
|                                         <small> |  | ||||||
|                                             {{ name|doc }} |  | ||||||
|                                         </small> |  | ||||||
|                                     </button> |  | ||||||
|                                     <div slot="modal"></div> |  | ||||||
|                                 </ak-modal-button> |  | ||||||
|                             </li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </ak-dropdown> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Local?' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Status' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for sc in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <span>{{ sc.name }}</span> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ sc|verbose_name }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ sc.local|yesno:"Yes,No" }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {% if sc.state.healthy %} |  | ||||||
|                             <i class="fas fa-check pf-m-success"></i> {{ sc.state.version }} |  | ||||||
|                             {% else %} |  | ||||||
|                             <i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %} |  | ||||||
|                             {% endif %} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-update' pk=sc.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-delete' pk=sc.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Outpost Service Connections.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any outposts." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no service connections exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                     </button> |  | ||||||
|                     <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                         {% for type, name in types.items %} |  | ||||||
|                         <li> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}"> |  | ||||||
|                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                     {{ name|verbose_name }}<br> |  | ||||||
|                                     <small> |  | ||||||
|                                         {{ name|doc }} |  | ||||||
|                                     </small> |  | ||||||
|                                 </button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                     </ul> |  | ||||||
|                 </ak-dropdown> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,148 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-infrastructure"></i> |  | ||||||
|             {% trans 'Policies' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                         </button> |  | ||||||
|                         <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                             {% for type, name in types.items %} |  | ||||||
|                             <li> |  | ||||||
|                                 <ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}"> |  | ||||||
|                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                         {{ name|verbose_name }}<br> |  | ||||||
|                                         <small> |  | ||||||
|                                             {{ name|doc }} |  | ||||||
|                                         </small> |  | ||||||
|                                     </button> |  | ||||||
|                                     <div slot="modal"></div> |  | ||||||
|                                 </ak-modal-button> |  | ||||||
|                             </li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </ak-dropdown> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for policy in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div> |  | ||||||
|                             <div>{{ policy.name }}</div> |  | ||||||
|                             {% if not policy.bindings.exists and not policy.promptstage_set.exists %} |  | ||||||
|                             <i class="pf-icon pf-icon-warning-triangle"></i> |  | ||||||
|                             <small>{% trans 'Warning: Policy is not assigned.' %}</small> |  | ||||||
|                             {% else %} |  | ||||||
|                             <i class="pf-icon pf-icon-ok"></i> |  | ||||||
|                             <small>{% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small> |  | ||||||
|                             {% endif %} |  | ||||||
|                         </div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ policy|verbose_name }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:policy-update' pk=policy.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:policy-test' pk=policy.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Test' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:policy-delete' pk=policy.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Policies.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any policies." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no policies exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                     </button> |  | ||||||
|                     <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                         {% for type, name in types.items %} |  | ||||||
|                         <li> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}"> |  | ||||||
|                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                     {{ name|verbose_name }}<br> |  | ||||||
|                                     <small> |  | ||||||
|                                         {{ name|doc }} |  | ||||||
|                                     </small> |  | ||||||
|                                 </button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                     </ul> |  | ||||||
|                 </ak-dropdown> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,46 +0,0 @@ | |||||||
| {% extends 'generic/form.html' %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1>{% blocktrans with policy=policy %}Test {{ policy }}{% endblocktrans %}</h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block beneath_form %} |  | ||||||
| {% if result %} |  | ||||||
| <div class="pf-c-form__group "> |  | ||||||
|     <div class="pf-c-form__group-label"> |  | ||||||
|         <label class="pf-c-form__label" for="context-1"> |  | ||||||
|             <span class="pf-c-form__label-text">{% trans 'Passing' %}</span> |  | ||||||
|         </label> |  | ||||||
|     </div> |  | ||||||
|     <div class="pf-c-form__group-label"> |  | ||||||
|         <div class="c-form__horizontal-group"> |  | ||||||
|             <span class="pf-c-form__label-text">{{ result.passing|yesno:"Yes,No" }}</span> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| <div class="pf-c-form__group "> |  | ||||||
|     <div class="pf-c-form__group-label"> |  | ||||||
|         <label class="pf-c-form__label" for="context-1"> |  | ||||||
|             <span class="pf-c-form__label-text">{% trans 'Messages' %}</span> |  | ||||||
|         </label> |  | ||||||
|     </div> |  | ||||||
|     <div class="pf-c-form__group-label"> |  | ||||||
|         <div class="c-form__horizontal-group"> |  | ||||||
|             <ul> |  | ||||||
|                 {% for m in result.messages %} |  | ||||||
|                 <li><span class="pf-c-form__label-text">{{ m }}</span></li> |  | ||||||
|                 {% empty %} |  | ||||||
|                 <li><span class="pf-c-form__label-text">-</span></li> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </ul> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| {% endif %} |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% trans 'Test' %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,119 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-infrastructure"></i> |  | ||||||
|             {% trans 'Policy Bindings' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Bind existing Policies to Models accepting policies." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Policy' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Enabled' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Timeout' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for pbm in object_list %} |  | ||||||
|                     <tr role="role"> |  | ||||||
|                         <td> |  | ||||||
|                             {{ pbm }} |  | ||||||
|                             <small> |  | ||||||
|                                 {{ pbm|fieldtype }} |  | ||||||
|                             </small> |  | ||||||
|                         </td> |  | ||||||
|                         <td></td> |  | ||||||
|                         <td></td> |  | ||||||
|                         <td></td> |  | ||||||
|                         <td></td> |  | ||||||
|                     </tr> |  | ||||||
|                     {% for binding in pbm.bindings %} |  | ||||||
|                     <tr class="row pf-c-table__expandable-row pf-m-expanded"> |  | ||||||
|                         <th role="cell"> |  | ||||||
|                             <div>{{ binding.policy }}</div> |  | ||||||
|                             <small> |  | ||||||
|                                 {{ binding.policy|fieldtype }} |  | ||||||
|                             </small> |  | ||||||
|                         </th> |  | ||||||
|                         <th role="cell"> |  | ||||||
|                             <div>{{ binding.enabled }}</div> |  | ||||||
|                         </th> |  | ||||||
|                         <th role="cell"> |  | ||||||
|                             <div>{{ binding.order }}</div> |  | ||||||
|                         </th> |  | ||||||
|                         <th role="cell"> |  | ||||||
|                             <div>{{ binding.timeout }}</div> |  | ||||||
|                         </th> |  | ||||||
|                         <td> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:policy-binding-update' pk=binding.pk %}"> |  | ||||||
|                                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                     {% trans 'Edit' %} |  | ||||||
|                                 </ak-spinner-button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:policy-binding-delete' pk=binding.pk %}"> |  | ||||||
|                                 <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                     {% trans 'Delete' %} |  | ||||||
|                                 </ak-spinner-button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </td> |  | ||||||
|                     </tr> |  | ||||||
|                     {% endfor %} |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Policy Bindings.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                     {% trans 'Currently no policy bindings exist. Click the button below to create one.' %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| {% extends 'generic/form.html' %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1>{% blocktrans with property_mapping=property_mapping %}Test {{ property_mapping }}{% endblocktrans %}</h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block beneath_form %} |  | ||||||
| {% if result %} |  | ||||||
| <div class="pf-c-form__group "> |  | ||||||
|     <div class="pf-c-form__group-label"> |  | ||||||
|         <label class="pf-c-form__label" for="context-1"> |  | ||||||
|             <span class="pf-c-form__label-text">{% trans 'Result' %}</span> |  | ||||||
|         </label> |  | ||||||
|     </div> |  | ||||||
|     <div class="pf-c-form__group-control"> |  | ||||||
|         <div class="c-form__horizontal-group"> |  | ||||||
|             <ak-codemirror mode="javascript"><textarea class="pf-c-form-control">{{ result }}</textarea></ak-codemirror> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| {% endif %} |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% trans 'Test' %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,148 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-middleware"></i> |  | ||||||
|             {% trans 'Source' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "External Sources which can be used to get Identities into authentik, for example Social Providers like Twiter and GitHub or Enterprise Providers like ADFS and LDAP." %} |  | ||||||
|         </p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                         </button> |  | ||||||
|                         <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                             {% for type, name in types.items %} |  | ||||||
|                             <li> |  | ||||||
|                                 <ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}"> |  | ||||||
|                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                         {{ name|verbose_name }}<br> |  | ||||||
|                                         <small> |  | ||||||
|                                             {{ name|doc }} |  | ||||||
|                                         </small> |  | ||||||
|                                     </button> |  | ||||||
|                                     <div slot="modal"></div> |  | ||||||
|                                 </ak-modal-button> |  | ||||||
|                             </li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </ak-dropdown> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Additional Info' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for source in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <a href="/sources/{{ source.slug }}"> |  | ||||||
|                             <div>{{ source.name }}</div> |  | ||||||
|                             {% if not source.enabled %} |  | ||||||
|                             <small>{% trans 'Disabled' %}</small> |  | ||||||
|                             {% endif %} |  | ||||||
|                         </a> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ source|fieldtype }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ source.ui_additional_info|default:""|safe }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:source-update' pk=source.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:source-delete' pk=source.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-middleware pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Sources.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any sources." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no sources exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                     </button> |  | ||||||
|                     <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                         {% for type, name in types.items %} |  | ||||||
|                         <li> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}"> |  | ||||||
|                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                     {{ name|verbose_name }}<br> |  | ||||||
|                                     <small> |  | ||||||
|                                         {{ name|doc }} |  | ||||||
|                                     </small> |  | ||||||
|                                 </button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                     </ul> |  | ||||||
|                 </ak-dropdown> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,143 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-plugged"></i> |  | ||||||
|             {% trans 'Stages' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Stages are single steps of a Flow that a user is guided through." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                         </button> |  | ||||||
|                         <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                             {% for type, name in types.items %} |  | ||||||
|                             <li> |  | ||||||
|                                 <ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}"> |  | ||||||
|                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                         {{ name|verbose_name }}<br> |  | ||||||
|                                         <small> |  | ||||||
|                                             {{ name|doc }} |  | ||||||
|                                         </small> |  | ||||||
|                                     </button> |  | ||||||
|                                     <div slot="modal"></div> |  | ||||||
|                                 </ak-modal-button> |  | ||||||
|                             </li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </ak-dropdown> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Flows' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for stage in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div> |  | ||||||
|                             <div>{{ stage.name }}</div> |  | ||||||
|                             <small>{{ stage|verbose_name }}</small> |  | ||||||
|                         </div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <ul> |  | ||||||
|                             {% for flow in stage.flow_set.all %} |  | ||||||
|                             <li>{{ flow.slug }}<</li> |  | ||||||
|                             {% empty %} |  | ||||||
|                             <li>-</li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:stage-update' pk=stage.stage_uuid %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:stage-delete' pk=stage.stage_uuid %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Stages.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any stages." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no stages exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-dropdown class="pf-c-dropdown"> |  | ||||||
|                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> |  | ||||||
|                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> |  | ||||||
|                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> |  | ||||||
|                     </button> |  | ||||||
|                     <ul class="pf-c-dropdown__menu" hidden> |  | ||||||
|                         {% for type, name in types.items %} |  | ||||||
|                         <li> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}"> |  | ||||||
|                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> |  | ||||||
|                                     {{ name|verbose_name }}<br> |  | ||||||
|                                     <small> |  | ||||||
|                                         {{ name|doc }} |  | ||||||
|                                     </small> |  | ||||||
|                                 </button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                     </ul> |  | ||||||
|                 </ak-dropdown> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,125 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-infrastructure"></i> |  | ||||||
|             {% trans 'Stage Bindings' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Bind existing Stages to Flows." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Stage Type' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% regroup object_list by target as grouped_bindings %} |  | ||||||
|                 {% for flow in grouped_bindings %} |  | ||||||
|                     <tr role="role"> |  | ||||||
|                         <td> |  | ||||||
|                             {% blocktrans with slug=flow.grouper.slug %} |  | ||||||
|                             Flow {{ slug }} |  | ||||||
|                             {% endblocktrans %} |  | ||||||
|                         </td> |  | ||||||
|                         <td></td> |  | ||||||
|                         <td></td> |  | ||||||
|                         <td></td> |  | ||||||
|                     </tr> |  | ||||||
|                     {% for binding in flow.list %} |  | ||||||
|                     <tr class="pf-c-table__expandable-row pf-m-expanded" role="row"> |  | ||||||
|                         <td role="cell"> |  | ||||||
|                             <span> |  | ||||||
|                                 {{ binding.order }} |  | ||||||
|                             </span> |  | ||||||
|                         </td> |  | ||||||
|                         <th role="columnheader"> |  | ||||||
|                             <div> |  | ||||||
|                                 <div>{{ binding.target.slug }}</div> |  | ||||||
|                                 <small> |  | ||||||
|                                     {{ binding.target.name }} |  | ||||||
|                                 </small> |  | ||||||
|                             </div> |  | ||||||
|                         </th> |  | ||||||
|                         <td role="cell"> |  | ||||||
|                             <div> |  | ||||||
|                                 <div> |  | ||||||
|                                     {{ binding.stage.name }} |  | ||||||
|                                 </div> |  | ||||||
|                                 <small> |  | ||||||
|                                     {{ binding.stage }} |  | ||||||
|                                 </small> |  | ||||||
|                             </div> |  | ||||||
|                         </td> |  | ||||||
|                         <td> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:stage-binding-update' pk=binding.pk %}"> |  | ||||||
|                                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                     {% trans 'Update' %} |  | ||||||
|                                 </ak-spinner-button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                             <ak-modal-button href="{% url 'authentik_admin:stage-binding-delete' pk=binding.pk %}"> |  | ||||||
|                                 <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                     {% trans 'Delete' %} |  | ||||||
|                                 </ak-spinner-button> |  | ||||||
|                                 <div slot="modal"></div> |  | ||||||
|                             </ak-modal-button> |  | ||||||
|                         </td> |  | ||||||
|                     </tr> |  | ||||||
|                     {% endfor %} |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Flow-Stage Bindings.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                     {% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,109 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-migration"></i> |  | ||||||
|             {% trans 'Invitations' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %} |  | ||||||
|         </p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'ID' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Created by' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Expiry' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for invitation in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ invitation.invite_uuid }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ invitation.created_by }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ invitation.expiry|default:"-" }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:stage-invitation-delete' pk=invitation.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Invitations.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any invitations." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no invitations exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,125 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-plugged"></i> |  | ||||||
|             {% trans 'Prompts' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Single Prompts that can be used for Prompt Stages." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:stage-prompt-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Field' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Label' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Flows' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for prompt in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div> |  | ||||||
|                             <div>{{ prompt.field_key }}</div> |  | ||||||
|                         </div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <div> |  | ||||||
|                             {{ prompt.label }} |  | ||||||
|                         </div> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <div> |  | ||||||
|                             {{ prompt.type }} |  | ||||||
|                         </div> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <div> |  | ||||||
|                             {{ prompt.order }} |  | ||||||
|                         </div> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <ul> |  | ||||||
|                             {% for flow in prompt.flow_set.all %} |  | ||||||
|                             <li>{{ flow.slug }}</li> |  | ||||||
|                             {% empty %} |  | ||||||
|                             <li>-</li> |  | ||||||
|                             {% endfor %} |  | ||||||
|                         </ul> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:stage-prompt-update' pk=prompt.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Update' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:stage-prompt-delete' pk=prompt.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Stage Prompts.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any stage prompts." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no stage prompts exist. Click the button below to create one.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <a href="{% url 'authentik_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,84 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load humanize %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-automation"></i> |  | ||||||
|             {% trans 'System Tasks' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Long-running operations which authentik executes in the background." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                     {% trans 'Refresh' %} |  | ||||||
|                 </button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Description' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Last Run' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Status' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Messages' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for task in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <span>{{ task.html_name|join:"_­" }}</span> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ task.task_description }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ task.finish_timestamp|naturaltime }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {% if task.result.status == task_successful %} |  | ||||||
|                             <i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %} |  | ||||||
|                             {% elif task.result.status == task_warning %} |  | ||||||
|                             <i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %} |  | ||||||
|                             {% elif task.result.status == task_error %} |  | ||||||
|                             <i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %} |  | ||||||
|                             {% else %} |  | ||||||
|                             <i class="fas fa-question-circle"></i> {% trans 'Unknown' %} |  | ||||||
|                             {% endif %} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         {% for message in task.result.messages %} |  | ||||||
|                         <div> |  | ||||||
|                             {{ message }} |  | ||||||
|                         </div> |  | ||||||
|                         {% endfor %} |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-action-button url="{% url 'authentik_api:admin_system_tasks-retry' pk=task.task_name %}"> |  | ||||||
|                             {% trans 'Retry Task' %} |  | ||||||
|                         </ak-action-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,102 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-security"></i> |  | ||||||
|             {% trans 'Tokens' %} |  | ||||||
|         </h1> |  | ||||||
|         <p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'User' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Expires?' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for token in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div>{{ token.identifier }}</div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ token.user }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ token.expiring|yesno:"Yes,No" }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {% if not token.expiring %} |  | ||||||
|                             - |  | ||||||
|                             {% else %} |  | ||||||
|                             {{ token.expires }} |  | ||||||
|                             {% endif %} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-danger"> |  | ||||||
|                                 {% trans 'Delete' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         <ak-token-copy-button identifier="{{ token.identifier }}"> |  | ||||||
|                             {% trans 'Copy token' %} |  | ||||||
|                         </ak-token-copy-button> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Tokens.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any token." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no tokens exist.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,42 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         {% block above_form %} |  | ||||||
|         <h1> |  | ||||||
|             {% blocktrans with object_type=object|verbose_name %} |  | ||||||
|             Disable {{ object_type }} |  | ||||||
|             {% endblocktrans %} |  | ||||||
|         </h1> |  | ||||||
|         {% endblock %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section"> |  | ||||||
|     <div class="pf-l-stack"> |  | ||||||
|         <div class="pf-l-stack__item"> |  | ||||||
|             <div class="pf-c-card"> |  | ||||||
|                 <div class="pf-c-card__body"> |  | ||||||
|                     <form action="" method="post" class="pf-c-form"> |  | ||||||
|                         {% csrf_token %} |  | ||||||
|                         <p> |  | ||||||
|                             {% blocktrans with object_type=object|verbose_name name=object %} |  | ||||||
|                             Are you sure you want to disable {{ object_type }} "{{ object }}"? |  | ||||||
|                             {% endblocktrans %} |  | ||||||
|                         </p> |  | ||||||
|                         <div class="pf-c-form__group pf-m-action"> |  | ||||||
|                             <div class="pf-c-form__actions"> |  | ||||||
|                                 <input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" /> |  | ||||||
|                                 <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </form> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,125 +0,0 @@ | |||||||
| {% extends "administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         <h1> |  | ||||||
|             <i class="pf-icon pf-icon-user"></i> |  | ||||||
|             {% trans 'Users' %} |  | ||||||
|         </h1> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section pf-m-no-padding-mobile"> |  | ||||||
|     <div class="pf-c-card"> |  | ||||||
|         {% if object_list %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|                 <div class="pf-c-toolbar__bulk-select"> |  | ||||||
|                     <ak-modal-button href="{% url 'authentik_admin:user-create' %}"> |  | ||||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                             {% trans 'Create' %} |  | ||||||
|                         </ak-spinner-button> |  | ||||||
|                         <div slot="modal"></div> |  | ||||||
|                     </ak-modal-button> |  | ||||||
|                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> |  | ||||||
|                         {% trans 'Refresh' %} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|                 {% include 'partials/pagination.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> |  | ||||||
|             <thead> |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Active' %}</th> |  | ||||||
|                     <th role="columnheader" scope="col">{% trans 'Last Login' %}</th> |  | ||||||
|                     <th role="cell"></th> |  | ||||||
|                 </tr> |  | ||||||
|             </thead> |  | ||||||
|             <tbody role="rowgroup"> |  | ||||||
|                 {% for user in object_list %} |  | ||||||
|                 <tr role="row"> |  | ||||||
|                     <th role="columnheader"> |  | ||||||
|                         <div> |  | ||||||
|                             <div>{{ user.username }}</div> |  | ||||||
|                             <small>{{ user.name }}</small> |  | ||||||
|                         </div> |  | ||||||
|                     </th> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ user.is_active }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td role="cell"> |  | ||||||
|                         <span> |  | ||||||
|                             {{ user.last_login }} |  | ||||||
|                         </span> |  | ||||||
|                     </td> |  | ||||||
|                     <td> |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:user-update' pk=user.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> |  | ||||||
|                                 {% trans 'Edit' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         {% if user.is_active %} |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:user-disable' pk=user.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-warning"> |  | ||||||
|                                 {% trans 'Disable' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         {% else %} |  | ||||||
|                         <ak-modal-button href="{% url 'authentik_admin:user-delete' pk=user.pk %}"> |  | ||||||
|                             <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                                 {% trans 'Enable' %} |  | ||||||
|                             </ak-spinner-button> |  | ||||||
|                             <div slot="modal"></div> |  | ||||||
|                         </ak-modal-button> |  | ||||||
|                         {% endif %} |  | ||||||
|                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a> |  | ||||||
|                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a> |  | ||||||
|                     </td> |  | ||||||
|                 </tr> |  | ||||||
|                 {% endfor %} |  | ||||||
|             </tbody> |  | ||||||
|         </table> |  | ||||||
|         <div class="pf-c-pagination pf-m-bottom"> |  | ||||||
|             {% include 'partials/pagination.html' %} |  | ||||||
|         </div> |  | ||||||
|         {% else %} |  | ||||||
|         <div class="pf-c-toolbar"> |  | ||||||
|             <div class="pf-c-toolbar__content"> |  | ||||||
|                 {% include 'partials/toolbar_search.html' %} |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="pf-c-empty-state"> |  | ||||||
|             <div class="pf-c-empty-state__content"> |  | ||||||
|                 <i class="pf-icon pf-icon-user pf-c-empty-state__icon" aria-hidden="true"></i> |  | ||||||
|                 <h1 class="pf-c-title pf-m-lg"> |  | ||||||
|                     {% trans 'No Users.' %} |  | ||||||
|                 </h1> |  | ||||||
|                 <div class="pf-c-empty-state__body"> |  | ||||||
|                 {% if request.GET.search != "" %} |  | ||||||
|                     {% trans "Your search query doesn't match any users." %} |  | ||||||
|                 {% else %} |  | ||||||
|                     {% trans 'Currently no users exist. How did you even get here.' %} |  | ||||||
|                 {% endif %} |  | ||||||
|                 </div> |  | ||||||
|                 <ak-modal-button href="{% url 'authentik_admin:user-create' %}"> |  | ||||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> |  | ||||||
|                         {% trans 'Create' %} |  | ||||||
|                     </ak-spinner-button> |  | ||||||
|                     <div slot="modal"></div> |  | ||||||
|                 </ak-modal-button> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| {% endblock %} |  | ||||||
| @ -1 +0,0 @@ | |||||||
| <ak-codemirror mode="{{ widget.attrs.mode }}"><textarea class="pf-c-form-control" name="{{ widget.name }}">{% if widget.value %}{{ widget.value }}{% endif %}</textarea></ak-codemirror> |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| {% extends base_template|default:"generic/form.html" %} |  | ||||||
|  |  | ||||||
| {% load authentik_utils %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1> |  | ||||||
|     {% blocktrans with type=form|form_verbose_name %} |  | ||||||
|     Create {{ type }} |  | ||||||
|     {% endblocktrans %} |  | ||||||
| </h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% blocktrans with type=form|form_verbose_name %} |  | ||||||
| Create {{ type }} |  | ||||||
| {% endblocktrans %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| {% extends container_template|default:"administration/base.html" %} |  | ||||||
|  |  | ||||||
| {% load i18n %} |  | ||||||
| {% load authentik_utils %} |  | ||||||
| {% load static %} |  | ||||||
|  |  | ||||||
| {% block content %} |  | ||||||
| <section class="pf-c-page__main-section pf-m-light"> |  | ||||||
|     <div class="pf-c-content"> |  | ||||||
|         {% block above_form %} |  | ||||||
|         {% endblock %} |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <section class="pf-c-page__main-section"> |  | ||||||
|     <div class="pf-l-stack"> |  | ||||||
|         <div class="pf-l-stack__item"> |  | ||||||
|             <div class="pf-c-card"> |  | ||||||
|                 <div class="pf-c-card__body"> |  | ||||||
|                     <form id="main-form" action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data"> |  | ||||||
|                         {% include 'partials/form_horizontal.html' with form=form %} |  | ||||||
|                         {% block beneath_form %} |  | ||||||
|                         {% endblock %} |  | ||||||
|                     </form> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </section> |  | ||||||
| <footer class="pf-c-modal-box__footer"> |  | ||||||
|     <input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" /> |  | ||||||
|     <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a> |  | ||||||
| </footer> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block scripts %} |  | ||||||
| {{ block.super }} |  | ||||||
| {{ form.media.js }} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,20 +0,0 @@ | |||||||
| {% extends base_template|default:"generic/form.html" %} |  | ||||||
|  |  | ||||||
| {% load authentik_utils %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1> |  | ||||||
|     {% trans form.title %} |  | ||||||
| </h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block beneath_form %} |  | ||||||
| <p> |  | ||||||
|     {% trans form.body %} |  | ||||||
| </p> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% trans 'Confirm' %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| {% extends base_template|default:"generic/form.html" %} |  | ||||||
|  |  | ||||||
| {% load authentik_utils %} |  | ||||||
| {% load i18n %} |  | ||||||
|  |  | ||||||
| {% block above_form %} |  | ||||||
| <h1> |  | ||||||
|     {% blocktrans with type=form|form_verbose_name|title inst=form.instance %} |  | ||||||
|     Update {{ inst }} |  | ||||||
|     {% endblocktrans %} |  | ||||||
| </h1> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block action %} |  | ||||||
| {% blocktrans with type=form|form_verbose_name %} |  | ||||||
| Update {{ type }} |  | ||||||
| {% endblocktrans %} |  | ||||||
| {% endblock %} |  | ||||||
| @ -1,8 +1,8 @@ | |||||||
| """test admin api""" | """test admin api""" | ||||||
| from json import loads | from json import loads | ||||||
|  |  | ||||||
| from django.shortcuts import reverse |  | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
| from authentik import __version__ | from authentik import __version__ | ||||||
| from authentik.core.models import Group, User | from authentik.core.models import Group, User | ||||||
| @ -27,7 +27,7 @@ class TestAdminAPI(TestCase): | |||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|         body = loads(response.content) |         body = loads(response.content) | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|             any([task["task_name"] == "clean_expired_models" for task in body]) |             any(task["task_name"] == "clean_expired_models" for task in body) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_tasks_retry(self): |     def test_tasks_retry(self): | ||||||
| @ -39,9 +39,7 @@ class TestAdminAPI(TestCase): | |||||||
|                 kwargs={"pk": "clean_expired_models"}, |                 kwargs={"pk": "clean_expired_models"}, | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 204) | ||||||
|         body = loads(response.content) |  | ||||||
|         self.assertTrue(body["successful"]) |  | ||||||
|  |  | ||||||
|     def test_tasks_retry_404(self): |     def test_tasks_retry_404(self): | ||||||
|         """Test Task API (retry, 404)""" |         """Test Task API (retry, 404)""" | ||||||
| @ -71,3 +69,8 @@ class TestAdminAPI(TestCase): | |||||||
|         """Test metrics API""" |         """Test metrics API""" | ||||||
|         response = self.client.get(reverse("authentik_api:admin_metrics-list")) |         response = self.client.get(reverse("authentik_api:admin_metrics-list")) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |     def test_apps(self): | ||||||
|  |         """Test apps API""" | ||||||
|  |         response = self.client.get(reverse("authentik_api:apps-list")) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  | |||||||
| @ -1,66 +0,0 @@ | |||||||
| """admin tests""" |  | ||||||
| from importlib import import_module |  | ||||||
| from typing import Callable |  | ||||||
|  |  | ||||||
| from django.forms import ModelForm |  | ||||||
| from django.shortcuts import reverse |  | ||||||
| from django.test import Client, TestCase |  | ||||||
| from django.urls.exceptions import NoReverseMatch |  | ||||||
|  |  | ||||||
| from authentik.admin.urls import urlpatterns |  | ||||||
| from authentik.core.models import Group, User |  | ||||||
| from authentik.lib.utils.reflection import get_apps |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAdmin(TestCase): |  | ||||||
|     """Generic admin tests""" |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         self.user = User.objects.create_user(username="test") |  | ||||||
|         self.user.ak_groups.add(Group.objects.filter(is_superuser=True).first()) |  | ||||||
|         self.user.save() |  | ||||||
|         self.client = Client() |  | ||||||
|         self.client.force_login(self.user) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def generic_view_tester(view_name: str) -> Callable: |  | ||||||
|     """This is used instead of subTest for better visibility""" |  | ||||||
|  |  | ||||||
|     def tester(self: TestAdmin): |  | ||||||
|         try: |  | ||||||
|             full_url = reverse(f"authentik_admin:{view_name}") |  | ||||||
|             response = self.client.get(full_url) |  | ||||||
|             self.assertTrue(response.status_code < 500) |  | ||||||
|         except NoReverseMatch: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     return tester |  | ||||||
|  |  | ||||||
|  |  | ||||||
| for url in urlpatterns: |  | ||||||
|     method_name = url.name.replace("-", "_") |  | ||||||
|     setattr(TestAdmin, f"test_view_{method_name}", generic_view_tester(url.name)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def generic_form_tester(form: ModelForm) -> Callable: |  | ||||||
|     """Test a form""" |  | ||||||
|  |  | ||||||
|     def tester(self: TestAdmin): |  | ||||||
|         form_inst = form() |  | ||||||
|         self.assertFalse(form_inst.is_valid()) |  | ||||||
|  |  | ||||||
|     return tester |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Load the forms module from every app, so we have all forms loaded |  | ||||||
| for app in get_apps(): |  | ||||||
|     module = app.__module__.replace(".apps", ".forms") |  | ||||||
|     try: |  | ||||||
|         import_module(module) |  | ||||||
|     except ImportError: |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
| for form_class in ModelForm.__subclasses__(): |  | ||||||
|     setattr( |  | ||||||
|         TestAdmin, f"test_form_{form_class.__name__}", generic_form_tester(form_class) |  | ||||||
|     ) |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| """admin tests""" |  | ||||||
| from uuid import uuid4 |  | ||||||
|  |  | ||||||
| from django import forms |  | ||||||
| from django.test import TestCase |  | ||||||
| from django.test.client import RequestFactory |  | ||||||
|  |  | ||||||
| from authentik.admin.views.policies_bindings import PolicyBindingCreateView |  | ||||||
| from authentik.core.models import Application |  | ||||||
| from authentik.policies.forms import PolicyBindingForm |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestPolicyBindingView(TestCase): |  | ||||||
|     """Generic admin tests""" |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         self.factory = RequestFactory() |  | ||||||
|  |  | ||||||
|     def test_without_get_param(self): |  | ||||||
|         """Test PolicyBindingCreateView without get params""" |  | ||||||
|         request = self.factory.get("/") |  | ||||||
|         view = PolicyBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {}) |  | ||||||
|  |  | ||||||
|     def test_with_params_invalid(self): |  | ||||||
|         """Test PolicyBindingCreateView with invalid get params""" |  | ||||||
|         request = self.factory.get("/", {"target": uuid4()}) |  | ||||||
|         view = PolicyBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {}) |  | ||||||
|  |  | ||||||
|     def test_with_params(self): |  | ||||||
|         """Test PolicyBindingCreateView with get params""" |  | ||||||
|         target = Application.objects.create(name="test") |  | ||||||
|         request = self.factory.get("/", {"target": target.pk.hex}) |  | ||||||
|         view = PolicyBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {"target": target, "order": 0}) |  | ||||||
|  |  | ||||||
|         self.assertTrue( |  | ||||||
|             isinstance( |  | ||||||
|                 PolicyBindingForm(initial={"target": "foo"}).fields["target"].widget, |  | ||||||
|                 forms.HiddenInput, |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| """admin tests""" |  | ||||||
| from uuid import uuid4 |  | ||||||
|  |  | ||||||
| from django import forms |  | ||||||
| from django.test import TestCase |  | ||||||
| from django.test.client import RequestFactory |  | ||||||
|  |  | ||||||
| from authentik.admin.views.stages_bindings import StageBindingCreateView |  | ||||||
| from authentik.flows.forms import FlowStageBindingForm |  | ||||||
| from authentik.flows.models import Flow |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestStageBindingView(TestCase): |  | ||||||
|     """Generic admin tests""" |  | ||||||
|  |  | ||||||
|     def setUp(self): |  | ||||||
|         self.factory = RequestFactory() |  | ||||||
|  |  | ||||||
|     def test_without_get_param(self): |  | ||||||
|         """Test StageBindingCreateView without get params""" |  | ||||||
|         request = self.factory.get("/") |  | ||||||
|         view = StageBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {}) |  | ||||||
|  |  | ||||||
|     def test_with_params_invalid(self): |  | ||||||
|         """Test StageBindingCreateView with invalid get params""" |  | ||||||
|         request = self.factory.get("/", {"target": uuid4()}) |  | ||||||
|         view = StageBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {}) |  | ||||||
|  |  | ||||||
|     def test_with_params(self): |  | ||||||
|         """Test StageBindingCreateView with get params""" |  | ||||||
|         target = Flow.objects.create(name="test", slug="test") |  | ||||||
|         request = self.factory.get("/", {"target": target.pk.hex}) |  | ||||||
|         view = StageBindingCreateView(request=request) |  | ||||||
|         self.assertEqual(view.get_initial(), {"target": target, "order": 0}) |  | ||||||
|  |  | ||||||
|         self.assertTrue( |  | ||||||
|             isinstance( |  | ||||||
|                 FlowStageBindingForm(initial={"target": "foo"}).fields["target"].widget, |  | ||||||
|                 forms.HiddenInput, |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
| @ -1,387 +0,0 @@ | |||||||
| """authentik URL Configuration""" |  | ||||||
| from django.urls import path |  | ||||||
|  |  | ||||||
| from authentik.admin.views import ( |  | ||||||
|     applications, |  | ||||||
|     certificate_key_pair, |  | ||||||
|     events_notifications_rules, |  | ||||||
|     events_notifications_transports, |  | ||||||
|     flows, |  | ||||||
|     groups, |  | ||||||
|     outposts, |  | ||||||
|     outposts_service_connections, |  | ||||||
|     overview, |  | ||||||
|     policies, |  | ||||||
|     policies_bindings, |  | ||||||
|     property_mappings, |  | ||||||
|     providers, |  | ||||||
|     sources, |  | ||||||
|     stages, |  | ||||||
|     stages_bindings, |  | ||||||
|     stages_invitations, |  | ||||||
|     stages_prompts, |  | ||||||
|     tasks, |  | ||||||
|     tokens, |  | ||||||
|     users, |  | ||||||
| ) |  | ||||||
| from authentik.providers.saml.views.metadata import MetadataImportView |  | ||||||
|  |  | ||||||
| urlpatterns = [ |  | ||||||
|     path( |  | ||||||
|         "overview/cache/flow/", |  | ||||||
|         overview.FlowCacheClearView.as_view(), |  | ||||||
|         name="overview-clear-flow-cache", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "overview/cache/policy/", |  | ||||||
|         overview.PolicyCacheClearView.as_view(), |  | ||||||
|         name="overview-clear-policy-cache", |  | ||||||
|     ), |  | ||||||
|     # Applications |  | ||||||
|     path( |  | ||||||
|         "applications/create/", |  | ||||||
|         applications.ApplicationCreateView.as_view(), |  | ||||||
|         name="application-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "applications/<uuid:pk>/update/", |  | ||||||
|         applications.ApplicationUpdateView.as_view(), |  | ||||||
|         name="application-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "applications/<uuid:pk>/delete/", |  | ||||||
|         applications.ApplicationDeleteView.as_view(), |  | ||||||
|         name="application-delete", |  | ||||||
|     ), |  | ||||||
|     # Tokens |  | ||||||
|     path("tokens/", tokens.TokenListView.as_view(), name="tokens"), |  | ||||||
|     path( |  | ||||||
|         "tokens/<uuid:pk>/delete/", |  | ||||||
|         tokens.TokenDeleteView.as_view(), |  | ||||||
|         name="token-delete", |  | ||||||
|     ), |  | ||||||
|     # Sources |  | ||||||
|     path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"), |  | ||||||
|     path( |  | ||||||
|         "sources/<uuid:pk>/update/", |  | ||||||
|         sources.SourceUpdateView.as_view(), |  | ||||||
|         name="source-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "sources/<uuid:pk>/delete/", |  | ||||||
|         sources.SourceDeleteView.as_view(), |  | ||||||
|         name="source-delete", |  | ||||||
|     ), |  | ||||||
|     # Policies |  | ||||||
|     path("policies/", policies.PolicyListView.as_view(), name="policies"), |  | ||||||
|     path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), |  | ||||||
|     path( |  | ||||||
|         "policies/<uuid:pk>/update/", |  | ||||||
|         policies.PolicyUpdateView.as_view(), |  | ||||||
|         name="policy-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "policies/<uuid:pk>/delete/", |  | ||||||
|         policies.PolicyDeleteView.as_view(), |  | ||||||
|         name="policy-delete", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "policies/<uuid:pk>/test/", |  | ||||||
|         policies.PolicyTestView.as_view(), |  | ||||||
|         name="policy-test", |  | ||||||
|     ), |  | ||||||
|     # Policy bindings |  | ||||||
|     path( |  | ||||||
|         "policies/bindings/", |  | ||||||
|         policies_bindings.PolicyBindingListView.as_view(), |  | ||||||
|         name="policies-bindings", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "policies/bindings/create/", |  | ||||||
|         policies_bindings.PolicyBindingCreateView.as_view(), |  | ||||||
|         name="policy-binding-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "policies/bindings/<uuid:pk>/update/", |  | ||||||
|         policies_bindings.PolicyBindingUpdateView.as_view(), |  | ||||||
|         name="policy-binding-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "policies/bindings/<uuid:pk>/delete/", |  | ||||||
|         policies_bindings.PolicyBindingDeleteView.as_view(), |  | ||||||
|         name="policy-binding-delete", |  | ||||||
|     ), |  | ||||||
|     # Providers |  | ||||||
|     path( |  | ||||||
|         "providers/create/", |  | ||||||
|         providers.ProviderCreateView.as_view(), |  | ||||||
|         name="provider-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "providers/create/saml/from-metadata/", |  | ||||||
|         MetadataImportView.as_view(), |  | ||||||
|         name="provider-saml-from-metadata", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "providers/<int:pk>/update/", |  | ||||||
|         providers.ProviderUpdateView.as_view(), |  | ||||||
|         name="provider-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "providers/<int:pk>/delete/", |  | ||||||
|         providers.ProviderDeleteView.as_view(), |  | ||||||
|         name="provider-delete", |  | ||||||
|     ), |  | ||||||
|     # Stages |  | ||||||
|     path("stages/", stages.StageListView.as_view(), name="stages"), |  | ||||||
|     path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"), |  | ||||||
|     path( |  | ||||||
|         "stages/<uuid:pk>/update/", |  | ||||||
|         stages.StageUpdateView.as_view(), |  | ||||||
|         name="stage-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/<uuid:pk>/delete/", |  | ||||||
|         stages.StageDeleteView.as_view(), |  | ||||||
|         name="stage-delete", |  | ||||||
|     ), |  | ||||||
|     # Stage bindings |  | ||||||
|     path( |  | ||||||
|         "stages/bindings/", |  | ||||||
|         stages_bindings.StageBindingListView.as_view(), |  | ||||||
|         name="stage-bindings", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/bindings/create/", |  | ||||||
|         stages_bindings.StageBindingCreateView.as_view(), |  | ||||||
|         name="stage-binding-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/bindings/<uuid:pk>/update/", |  | ||||||
|         stages_bindings.StageBindingUpdateView.as_view(), |  | ||||||
|         name="stage-binding-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/bindings/<uuid:pk>/delete/", |  | ||||||
|         stages_bindings.StageBindingDeleteView.as_view(), |  | ||||||
|         name="stage-binding-delete", |  | ||||||
|     ), |  | ||||||
|     # Stage Prompts |  | ||||||
|     path( |  | ||||||
|         "stages_prompts/", |  | ||||||
|         stages_prompts.PromptListView.as_view(), |  | ||||||
|         name="stage-prompts", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages_prompts/create/", |  | ||||||
|         stages_prompts.PromptCreateView.as_view(), |  | ||||||
|         name="stage-prompt-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages_prompts/<uuid:pk>/update/", |  | ||||||
|         stages_prompts.PromptUpdateView.as_view(), |  | ||||||
|         name="stage-prompt-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages_prompts/<uuid:pk>/delete/", |  | ||||||
|         stages_prompts.PromptDeleteView.as_view(), |  | ||||||
|         name="stage-prompt-delete", |  | ||||||
|     ), |  | ||||||
|     # Stage Invitations |  | ||||||
|     path( |  | ||||||
|         "stages/invitations/", |  | ||||||
|         stages_invitations.InvitationListView.as_view(), |  | ||||||
|         name="stage-invitations", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/invitations/create/", |  | ||||||
|         stages_invitations.InvitationCreateView.as_view(), |  | ||||||
|         name="stage-invitation-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "stages/invitations/<uuid:pk>/delete/", |  | ||||||
|         stages_invitations.InvitationDeleteView.as_view(), |  | ||||||
|         name="stage-invitation-delete", |  | ||||||
|     ), |  | ||||||
|     # Flows |  | ||||||
|     path("flows/", flows.FlowListView.as_view(), name="flows"), |  | ||||||
|     path( |  | ||||||
|         "flows/create/", |  | ||||||
|         flows.FlowCreateView.as_view(), |  | ||||||
|         name="flow-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "flows/import/", |  | ||||||
|         flows.FlowImportView.as_view(), |  | ||||||
|         name="flow-import", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "flows/<uuid:pk>/update/", |  | ||||||
|         flows.FlowUpdateView.as_view(), |  | ||||||
|         name="flow-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "flows/<uuid:pk>/execute/", |  | ||||||
|         flows.FlowDebugExecuteView.as_view(), |  | ||||||
|         name="flow-execute", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "flows/<uuid:pk>/export/", |  | ||||||
|         flows.FlowExportView.as_view(), |  | ||||||
|         name="flow-export", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "flows/<uuid:pk>/delete/", |  | ||||||
|         flows.FlowDeleteView.as_view(), |  | ||||||
|         name="flow-delete", |  | ||||||
|     ), |  | ||||||
|     # Property Mappings |  | ||||||
|     path( |  | ||||||
|         "property-mappings/create/", |  | ||||||
|         property_mappings.PropertyMappingCreateView.as_view(), |  | ||||||
|         name="property-mapping-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "property-mappings/<uuid:pk>/update/", |  | ||||||
|         property_mappings.PropertyMappingUpdateView.as_view(), |  | ||||||
|         name="property-mapping-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "property-mappings/<uuid:pk>/delete/", |  | ||||||
|         property_mappings.PropertyMappingDeleteView.as_view(), |  | ||||||
|         name="property-mapping-delete", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "property-mappings/<uuid:pk>/test/", |  | ||||||
|         property_mappings.PropertyMappingTestView.as_view(), |  | ||||||
|         name="property-mapping-test", |  | ||||||
|     ), |  | ||||||
|     # Users |  | ||||||
|     path("users/", users.UserListView.as_view(), name="users"), |  | ||||||
|     path("users/create/", users.UserCreateView.as_view(), name="user-create"), |  | ||||||
|     path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"), |  | ||||||
|     path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"), |  | ||||||
|     path( |  | ||||||
|         "users/<int:pk>/disable/", users.UserDisableView.as_view(), name="user-disable" |  | ||||||
|     ), |  | ||||||
|     path("users/<int:pk>/enable/", users.UserEnableView.as_view(), name="user-enable"), |  | ||||||
|     path( |  | ||||||
|         "users/<int:pk>/reset/", |  | ||||||
|         users.UserPasswordResetView.as_view(), |  | ||||||
|         name="user-password-reset", |  | ||||||
|     ), |  | ||||||
|     # Groups |  | ||||||
|     path("groups/", groups.GroupListView.as_view(), name="groups"), |  | ||||||
|     path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), |  | ||||||
|     path( |  | ||||||
|         "groups/<uuid:pk>/update/", |  | ||||||
|         groups.GroupUpdateView.as_view(), |  | ||||||
|         name="group-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "groups/<uuid:pk>/delete/", |  | ||||||
|         groups.GroupDeleteView.as_view(), |  | ||||||
|         name="group-delete", |  | ||||||
|     ), |  | ||||||
|     # Certificate-Key Pairs |  | ||||||
|     path( |  | ||||||
|         "crypto/certificates/", |  | ||||||
|         certificate_key_pair.CertificateKeyPairListView.as_view(), |  | ||||||
|         name="certificate_key_pair", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "crypto/certificates/create/", |  | ||||||
|         certificate_key_pair.CertificateKeyPairCreateView.as_view(), |  | ||||||
|         name="certificatekeypair-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "crypto/certificates/generate/", |  | ||||||
|         certificate_key_pair.CertificateKeyPairGenerateView.as_view(), |  | ||||||
|         name="certificatekeypair-generate", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "crypto/certificates/<uuid:pk>/update/", |  | ||||||
|         certificate_key_pair.CertificateKeyPairUpdateView.as_view(), |  | ||||||
|         name="certificatekeypair-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "crypto/certificates/<uuid:pk>/delete/", |  | ||||||
|         certificate_key_pair.CertificateKeyPairDeleteView.as_view(), |  | ||||||
|         name="certificatekeypair-delete", |  | ||||||
|     ), |  | ||||||
|     # Outposts |  | ||||||
|     path( |  | ||||||
|         "outposts/create/", |  | ||||||
|         outposts.OutpostCreateView.as_view(), |  | ||||||
|         name="outpost-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "outposts/<uuid:pk>/update/", |  | ||||||
|         outposts.OutpostUpdateView.as_view(), |  | ||||||
|         name="outpost-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "outposts/<uuid:pk>/delete/", |  | ||||||
|         outposts.OutpostDeleteView.as_view(), |  | ||||||
|         name="outpost-delete", |  | ||||||
|     ), |  | ||||||
|     # Outpost Service Connections |  | ||||||
|     path( |  | ||||||
|         "outpost_service_connections/", |  | ||||||
|         outposts_service_connections.OutpostServiceConnectionListView.as_view(), |  | ||||||
|         name="outpost-service-connections", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "outpost_service_connections/create/", |  | ||||||
|         outposts_service_connections.OutpostServiceConnectionCreateView.as_view(), |  | ||||||
|         name="outpost-service-connection-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "outpost_service_connections/<uuid:pk>/update/", |  | ||||||
|         outposts_service_connections.OutpostServiceConnectionUpdateView.as_view(), |  | ||||||
|         name="outpost-service-connection-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "outpost_service_connections/<uuid:pk>/delete/", |  | ||||||
|         outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), |  | ||||||
|         name="outpost-service-connection-delete", |  | ||||||
|     ), |  | ||||||
|     # Tasks |  | ||||||
|     path( |  | ||||||
|         "tasks/", |  | ||||||
|         tasks.TaskListView.as_view(), |  | ||||||
|         name="tasks", |  | ||||||
|     ), |  | ||||||
|     # Event Notification Transpots |  | ||||||
|     path( |  | ||||||
|         "events/transports/create/", |  | ||||||
|         events_notifications_transports.NotificationTransportCreateView.as_view(), |  | ||||||
|         name="notification-transport-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "events/transports/<uuid:pk>/update/", |  | ||||||
|         events_notifications_transports.NotificationTransportUpdateView.as_view(), |  | ||||||
|         name="notification-transport-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "events/transports/<uuid:pk>/delete/", |  | ||||||
|         events_notifications_transports.NotificationTransportDeleteView.as_view(), |  | ||||||
|         name="notification-transport-delete", |  | ||||||
|     ), |  | ||||||
|     # Event Notification Rules |  | ||||||
|     path( |  | ||||||
|         "events/rules/create/", |  | ||||||
|         events_notifications_rules.NotificationRuleCreateView.as_view(), |  | ||||||
|         name="notification-rule-create", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "events/rules/<uuid:pk>/update/", |  | ||||||
|         events_notifications_rules.NotificationRuleUpdateView.as_view(), |  | ||||||
|         name="notification-rule-update", |  | ||||||
|     ), |  | ||||||
|     path( |  | ||||||
|         "events/rules/<uuid:pk>/delete/", |  | ||||||
|         events_notifications_rules.NotificationRuleDeleteView.as_view(), |  | ||||||
|         name="notification-rule-delete", |  | ||||||
|     ), |  | ||||||
| ] |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| """authentik Application administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import UpdateView |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView |  | ||||||
| from authentik.core.forms.applications import ApplicationForm |  | ||||||
| from authentik.core.models import Application |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApplicationCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Application""" |  | ||||||
|  |  | ||||||
|     model = Application |  | ||||||
|     form_class = ApplicationForm |  | ||||||
|     permission_required = "authentik_core.add_application" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Application") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApplicationUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update application""" |  | ||||||
|  |  | ||||||
|     model = Application |  | ||||||
|     form_class = ApplicationForm |  | ||||||
|     permission_required = "authentik_core.change_application" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Application") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApplicationDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete application""" |  | ||||||
|  |  | ||||||
|     model = Application |  | ||||||
|     permission_required = "authentik_core.delete_application" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Application") |  | ||||||
| @ -1,120 +0,0 @@ | |||||||
| """authentik CertificateKeyPair administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http.response import HttpResponse |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView, UpdateView |  | ||||||
| from django.views.generic.edit import FormView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.crypto.builder import CertificateBuilder |  | ||||||
| from authentik.crypto.forms import ( |  | ||||||
|     CertificateKeyPairForm, |  | ||||||
|     CertificateKeyPairGenerateForm, |  | ||||||
| ) |  | ||||||
| from authentik.crypto.models import CertificateKeyPair |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CertificateKeyPairListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all keypairs""" |  | ||||||
|  |  | ||||||
|     model = CertificateKeyPair |  | ||||||
|     permission_required = "authentik_crypto.view_certificatekeypair" |  | ||||||
|     ordering = "name" |  | ||||||
|     template_name = "administration/certificatekeypair/list.html" |  | ||||||
|  |  | ||||||
|     search_fields = ["name"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CertificateKeyPairCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new CertificateKeyPair""" |  | ||||||
|  |  | ||||||
|     model = CertificateKeyPair |  | ||||||
|     form_class = CertificateKeyPairForm |  | ||||||
|     permission_required = "authentik_crypto.add_certificatekeypair" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") |  | ||||||
|     success_message = _("Successfully created Certificate-Key Pair") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CertificateKeyPairGenerateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     FormView, |  | ||||||
| ): |  | ||||||
|     """Generate new CertificateKeyPair""" |  | ||||||
|  |  | ||||||
|     model = CertificateKeyPair |  | ||||||
|     form_class = CertificateKeyPairGenerateForm |  | ||||||
|     permission_required = "authentik_crypto.add_certificatekeypair" |  | ||||||
|  |  | ||||||
|     template_name = "administration/certificatekeypair/generate.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") |  | ||||||
|     success_message = _("Successfully generated Certificate-Key Pair") |  | ||||||
|  |  | ||||||
|     def form_valid(self, form: CertificateKeyPairGenerateForm) -> HttpResponse: |  | ||||||
|         builder = CertificateBuilder() |  | ||||||
|         builder.common_name = form.data["common_name"] |  | ||||||
|         builder.build( |  | ||||||
|             subject_alt_names=form.data.get("subject_alt_name", "").split(","), |  | ||||||
|             validity_days=int(form.data["validity_days"]), |  | ||||||
|         ) |  | ||||||
|         builder.save() |  | ||||||
|         return super().form_valid(form) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CertificateKeyPairUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update certificatekeypair""" |  | ||||||
|  |  | ||||||
|     model = CertificateKeyPair |  | ||||||
|     form_class = CertificateKeyPairForm |  | ||||||
|     permission_required = "authentik_crypto.change_certificatekeypair" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") |  | ||||||
|     success_message = _("Successfully updated Certificate-Key Pair") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CertificateKeyPairDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete certificatekeypair""" |  | ||||||
|  |  | ||||||
|     model = CertificateKeyPair |  | ||||||
|     permission_required = "authentik_crypto.delete_certificatekeypair" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") |  | ||||||
|     success_message = _("Successfully deleted Certificate-Key Pair") |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| """authentik NotificationRule administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import UpdateView |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView |  | ||||||
| from authentik.events.forms import NotificationRuleForm |  | ||||||
| from authentik.events.models import NotificationRule |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationRuleCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new NotificationRule""" |  | ||||||
|  |  | ||||||
|     model = NotificationRule |  | ||||||
|     form_class = NotificationRuleForm |  | ||||||
|     permission_required = "authentik_events.add_NotificationRule" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Notification Rule") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationRuleUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update application""" |  | ||||||
|  |  | ||||||
|     model = NotificationRule |  | ||||||
|     form_class = NotificationRuleForm |  | ||||||
|     permission_required = "authentik_events.change_NotificationRule" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Notification Rule") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationRuleDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete application""" |  | ||||||
|  |  | ||||||
|     model = NotificationRule |  | ||||||
|     permission_required = "authentik_events.delete_NotificationRule" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Notification Rule") |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| """authentik NotificationTransport administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import UpdateView |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView |  | ||||||
| from authentik.events.forms import NotificationTransportForm |  | ||||||
| from authentik.events.models import NotificationTransport |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationTransportCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new NotificationTransport""" |  | ||||||
|  |  | ||||||
|     model = NotificationTransport |  | ||||||
|     form_class = NotificationTransportForm |  | ||||||
|     permission_required = "authentik_events.add_notificationtransport" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Notification Transport") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationTransportUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update application""" |  | ||||||
|  |  | ||||||
|     model = NotificationTransport |  | ||||||
|     form_class = NotificationTransportForm |  | ||||||
|     permission_required = "authentik_events.change_notificationtransport" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Notification Transport") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NotificationTransportDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete application""" |  | ||||||
|  |  | ||||||
|     model = NotificationTransport |  | ||||||
|     permission_required = "authentik_events.delete_notificationtransport" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Notification Transport") |  | ||||||
| @ -1,161 +0,0 @@ | |||||||
| """authentik Flow administration""" |  | ||||||
| from django.contrib import messages |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http import HttpRequest, HttpResponse, JsonResponse |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import DetailView, FormView, ListView, UpdateView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.flows.exceptions import FlowNonApplicableException |  | ||||||
| from authentik.flows.forms import FlowForm, FlowImportForm |  | ||||||
| from authentik.flows.models import Flow |  | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER |  | ||||||
| from authentik.flows.transfer.common import DataclassEncoder |  | ||||||
| from authentik.flows.transfer.exporter import FlowExporter |  | ||||||
| from authentik.flows.transfer.importer import FlowImporter |  | ||||||
| from authentik.flows.views import SESSION_KEY_PLAN, FlowPlanner |  | ||||||
| from authentik.lib.utils.urls import redirect_with_qs |  | ||||||
| from authentik.lib.views import CreateAssignPermView, bad_request_message |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all flows""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     permission_required = "authentik_flows.view_flow" |  | ||||||
|     ordering = "name" |  | ||||||
|     template_name = "administration/flow/list.html" |  | ||||||
|     search_fields = ["name", "slug", "designation", "title"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Flow""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     form_class = FlowForm |  | ||||||
|     permission_required = "authentik_flows.add_flow" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:flows") |  | ||||||
|     success_message = _("Successfully created Flow") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update flow""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     form_class = FlowForm |  | ||||||
|     permission_required = "authentik_flows.change_flow" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:flows") |  | ||||||
|     success_message = _("Successfully updated Flow") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete flow""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     permission_required = "authentik_flows.delete_flow" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:flows") |  | ||||||
|     success_message = _("Successfully deleted Flow") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): |  | ||||||
|     """Debug exectue flow, setting the current user as pending user""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     permission_required = "authentik_flows.view_flow" |  | ||||||
|  |  | ||||||
|     # pylint: disable=unused-argument |  | ||||||
|     def get(self, request: HttpRequest, pk: str) -> HttpResponse: |  | ||||||
|         """Debug exectue flow, setting the current user as pending user""" |  | ||||||
|         flow: Flow = self.get_object() |  | ||||||
|         planner = FlowPlanner(flow) |  | ||||||
|         planner.use_cache = False |  | ||||||
|         try: |  | ||||||
|             plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) |  | ||||||
|             self.request.session[SESSION_KEY_PLAN] = plan |  | ||||||
|         except FlowNonApplicableException as exc: |  | ||||||
|             return bad_request_message( |  | ||||||
|                 request, |  | ||||||
|                 _( |  | ||||||
|                     "Flow not applicable to current user/request: %(messages)s" |  | ||||||
|                     % {"messages": str(exc)} |  | ||||||
|                 ), |  | ||||||
|             ) |  | ||||||
|         return redirect_with_qs( |  | ||||||
|             "authentik_flows:flow-executor-shell", |  | ||||||
|             self.request.GET, |  | ||||||
|             flow_slug=flow.slug, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowImportView(LoginRequiredMixin, FormView): |  | ||||||
|     """Import flow from JSON Export; only allowed for superusers |  | ||||||
|     as these flows can contain python code""" |  | ||||||
|  |  | ||||||
|     form_class = FlowImportForm |  | ||||||
|     template_name = "administration/flow/import.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:flows") |  | ||||||
|  |  | ||||||
|     def dispatch(self, request, *args, **kwargs): |  | ||||||
|         if not request.user.is_superuser: |  | ||||||
|             return self.handle_no_permission() |  | ||||||
|         return super().dispatch(request, *args, **kwargs) |  | ||||||
|  |  | ||||||
|     def form_valid(self, form: FlowImportForm) -> HttpResponse: |  | ||||||
|         importer = FlowImporter(form.cleaned_data["flow"].read().decode()) |  | ||||||
|         successful = importer.apply() |  | ||||||
|         if not successful: |  | ||||||
|             messages.error(self.request, _("Failed to import flow.")) |  | ||||||
|         else: |  | ||||||
|             messages.success(self.request, _("Successfully imported flow.")) |  | ||||||
|         return super().form_valid(form) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowExportView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): |  | ||||||
|     """Export Flow""" |  | ||||||
|  |  | ||||||
|     model = Flow |  | ||||||
|     permission_required = "authentik_flows.export_flow" |  | ||||||
|  |  | ||||||
|     # pylint: disable=unused-argument |  | ||||||
|     def get(self, request: HttpRequest, pk: str) -> HttpResponse: |  | ||||||
|         """Debug exectue flow, setting the current user as pending user""" |  | ||||||
|         flow: Flow = self.get_object() |  | ||||||
|         exporter = FlowExporter(flow) |  | ||||||
|         response = JsonResponse(exporter.export(), encoder=DataclassEncoder, safe=False) |  | ||||||
|         response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"' |  | ||||||
|         return response |  | ||||||
| @ -1,83 +0,0 @@ | |||||||
| """authentik Group administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView, UpdateView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.core.forms.groups import GroupForm |  | ||||||
| from authentik.core.models import Group |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GroupListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all groups""" |  | ||||||
|  |  | ||||||
|     model = Group |  | ||||||
|     permission_required = "authentik_core.view_group" |  | ||||||
|     ordering = "name" |  | ||||||
|     template_name = "administration/group/list.html" |  | ||||||
|     search_fields = ["name", "attributes"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GroupCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Group""" |  | ||||||
|  |  | ||||||
|     model = Group |  | ||||||
|     form_class = GroupForm |  | ||||||
|     permission_required = "authentik_core.add_group" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:groups") |  | ||||||
|     success_message = _("Successfully created Group") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GroupUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update group""" |  | ||||||
|  |  | ||||||
|     model = Group |  | ||||||
|     form_class = GroupForm |  | ||||||
|     permission_required = "authentik_core.change_group" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:groups") |  | ||||||
|     success_message = _("Successfully updated Group") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete group""" |  | ||||||
|  |  | ||||||
|     model = Group |  | ||||||
|     permission_required = "authentik_flows.delete_group" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:groups") |  | ||||||
|     success_message = _("Successfully deleted Group") |  | ||||||
| @ -1,68 +0,0 @@ | |||||||
| """authentik Outpost administration""" |  | ||||||
| from dataclasses import asdict |  | ||||||
| from typing import Any, Dict |  | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import UpdateView |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
| from authentik.outposts.forms import OutpostForm |  | ||||||
| from authentik.outposts.models import Outpost, OutpostConfig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Outpost""" |  | ||||||
|  |  | ||||||
|     model = Outpost |  | ||||||
|     form_class = OutpostForm |  | ||||||
|     permission_required = "authentik_outposts.add_outpost" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Outpost") |  | ||||||
|  |  | ||||||
|     def get_initial(self) -> Dict[str, Any]: |  | ||||||
|         return { |  | ||||||
|             "_config": asdict( |  | ||||||
|                 OutpostConfig(authentik_host=self.request.build_absolute_uri("/")) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update outpost""" |  | ||||||
|  |  | ||||||
|     model = Outpost |  | ||||||
|     form_class = OutpostForm |  | ||||||
|     permission_required = "authentik_outposts.change_outpost" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Outpost") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete outpost""" |  | ||||||
|  |  | ||||||
|     model = Outpost |  | ||||||
|     permission_required = "authentik_outposts.delete_outpost" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Outpost") |  | ||||||
| @ -1,83 +0,0 @@ | |||||||
| """authentik OutpostServiceConnection administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceListView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.outposts.models import OutpostServiceConnection |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostServiceConnectionListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     InheritanceListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all outpost-service-connections""" |  | ||||||
|  |  | ||||||
|     model = OutpostServiceConnection |  | ||||||
|     permission_required = "authentik_outposts.add_outpostserviceconnection" |  | ||||||
|     template_name = "administration/outpost_service_connection/list.html" |  | ||||||
|     ordering = "pk" |  | ||||||
|     search_fields = ["pk", "name"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostServiceConnectionCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new OutpostServiceConnection""" |  | ||||||
|  |  | ||||||
|     model = OutpostServiceConnection |  | ||||||
|     permission_required = "authentik_outposts.add_outpostserviceconnection" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:outpost-service-connections") |  | ||||||
|     success_message = _("Successfully created OutpostServiceConnection") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostServiceConnectionUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update outpostserviceconnection""" |  | ||||||
|  |  | ||||||
|     model = OutpostServiceConnection |  | ||||||
|     permission_required = "authentik_outposts.change_outpostserviceconnection" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:outpost-service-connections") |  | ||||||
|     success_message = _("Successfully updated OutpostServiceConnection") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OutpostServiceConnectionDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete outpostserviceconnection""" |  | ||||||
|  |  | ||||||
|     model = OutpostServiceConnection |  | ||||||
|     permission_required = "authentik_outposts.delete_outpostserviceconnection" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:outpost-service-connections") |  | ||||||
|     success_message = _("Successfully deleted OutpostServiceConnection") |  | ||||||
| @ -1,47 +0,0 @@ | |||||||
| """authentik administration overview""" |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.core.cache import cache |  | ||||||
| from django.http.request import HttpRequest |  | ||||||
| from django.http.response import HttpResponse |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import FormView |  | ||||||
| from structlog.stdlib import get_logger |  | ||||||
|  |  | ||||||
| from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm |  | ||||||
| from authentik.admin.mixins import AdminRequiredMixin |  | ||||||
| from authentik.core.api.applications import user_app_cache_key |  | ||||||
|  |  | ||||||
| LOGGER = get_logger() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): |  | ||||||
|     """View to clear Policy cache""" |  | ||||||
|  |  | ||||||
|     form_class = PolicyCacheClearForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/form_non_model.html" |  | ||||||
|     success_message = _("Successfully cleared Policy cache") |  | ||||||
|  |  | ||||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: |  | ||||||
|         keys = cache.keys("policy_*") |  | ||||||
|         cache.delete_many(keys) |  | ||||||
|         LOGGER.debug("Cleared Policy cache", keys=len(keys)) |  | ||||||
|         # Also delete user application cache |  | ||||||
|         keys = cache.keys(user_app_cache_key("*")) |  | ||||||
|         cache.delete_many(keys) |  | ||||||
|         return super().post(request, *args, **kwargs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): |  | ||||||
|     """View to clear Flow cache""" |  | ||||||
|  |  | ||||||
|     form_class = FlowCacheClearForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/form_non_model.html" |  | ||||||
|     success_message = _("Successfully cleared Flow cache") |  | ||||||
|  |  | ||||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: |  | ||||||
|         keys = cache.keys("flow_*") |  | ||||||
|         cache.delete_many(keys) |  | ||||||
|         LOGGER.debug("Cleared flow cache", keys=len(keys)) |  | ||||||
|         return super().post(request, *args, **kwargs) |  | ||||||
| @ -1,126 +0,0 @@ | |||||||
| """authentik Policy administration""" |  | ||||||
| from typing import Any, Dict |  | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http import HttpResponse |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import FormView |  | ||||||
| from django.views.generic.detail import DetailView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.forms.policies import PolicyTestForm |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceListView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.policies.models import Policy, PolicyBinding |  | ||||||
| from authentik.policies.process import PolicyProcess, PolicyRequest |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     InheritanceListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all policies""" |  | ||||||
|  |  | ||||||
|     model = Policy |  | ||||||
|     permission_required = "authentik_policies.view_policy" |  | ||||||
|     ordering = "name" |  | ||||||
|     template_name = "administration/policy/list.html" |  | ||||||
|     search_fields = ["name"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new Policy""" |  | ||||||
|  |  | ||||||
|     model = Policy |  | ||||||
|     permission_required = "authentik_policies.add_policy" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies") |  | ||||||
|     success_message = _("Successfully created Policy") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update policy""" |  | ||||||
|  |  | ||||||
|     model = Policy |  | ||||||
|     permission_required = "authentik_policies.change_policy" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies") |  | ||||||
|     success_message = _("Successfully updated Policy") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete policy""" |  | ||||||
|  |  | ||||||
|     model = Policy |  | ||||||
|     permission_required = "authentik_policies.delete_policy" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies") |  | ||||||
|     success_message = _("Successfully deleted Policy") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView): |  | ||||||
|     """View to test policy(s)""" |  | ||||||
|  |  | ||||||
|     model = Policy |  | ||||||
|     form_class = PolicyTestForm |  | ||||||
|     permission_required = "authentik_policies.view_policy" |  | ||||||
|     template_name = "administration/policy/test.html" |  | ||||||
|     object = None |  | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None) -> Policy: |  | ||||||
|         return ( |  | ||||||
|             Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first() |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: |  | ||||||
|         kwargs["policy"] = self.get_object() |  | ||||||
|         return super().get_context_data(**kwargs) |  | ||||||
|  |  | ||||||
|     def post(self, *args, **kwargs) -> HttpResponse: |  | ||||||
|         self.object = self.get_object() |  | ||||||
|         return super().post(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def form_valid(self, form: PolicyTestForm) -> HttpResponse: |  | ||||||
|         policy = self.get_object() |  | ||||||
|         user = form.cleaned_data.get("user") |  | ||||||
|  |  | ||||||
|         p_request = PolicyRequest(user) |  | ||||||
|         p_request.debug = True |  | ||||||
|         p_request.http_request = self.request |  | ||||||
|         p_request.context = form.cleaned_data.get("context", {}) |  | ||||||
|  |  | ||||||
|         proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) |  | ||||||
|         result = proc.execute() |  | ||||||
|         context = self.get_context_data(form=form) |  | ||||||
|         context["result"] = result |  | ||||||
|         return self.render_to_response(context) |  | ||||||
| @ -1,117 +0,0 @@ | |||||||
| """authentik PolicyBinding administration""" |  | ||||||
| from typing import Any |  | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.db.models import Max, QuerySet |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView, UpdateView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
| from guardian.shortcuts import get_objects_for_user |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
| from authentik.policies.forms import PolicyBindingForm |  | ||||||
| from authentik.policies.models import PolicyBinding, PolicyBindingModel |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyBindingListView( |  | ||||||
|     LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView |  | ||||||
| ): |  | ||||||
|     """Show list of all policies""" |  | ||||||
|  |  | ||||||
|     model = PolicyBinding |  | ||||||
|     permission_required = "authentik_policies.view_policybinding" |  | ||||||
|     ordering = ["order", "target"] |  | ||||||
|     template_name = "administration/policy_binding/list.html" |  | ||||||
|  |  | ||||||
|     def get_queryset(self) -> QuerySet: |  | ||||||
|         # Since `select_subclasses` does not work with a foreign key, we have to do two queries here |  | ||||||
|         # First, get all pbm objects that have bindings attached |  | ||||||
|         objects = ( |  | ||||||
|             get_objects_for_user( |  | ||||||
|                 self.request.user, "authentik_policies.view_policybindingmodel" |  | ||||||
|             ) |  | ||||||
|             .filter(policies__isnull=False) |  | ||||||
|             .select_subclasses() |  | ||||||
|             .select_related() |  | ||||||
|             .order_by("pk") |  | ||||||
|         ) |  | ||||||
|         for pbm in objects: |  | ||||||
|             pbm.bindings = get_objects_for_user( |  | ||||||
|                 self.request.user, self.permission_required |  | ||||||
|             ).filter(target__pk=pbm.pbm_uuid) |  | ||||||
|         return objects |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyBindingCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new PolicyBinding""" |  | ||||||
|  |  | ||||||
|     model = PolicyBinding |  | ||||||
|     permission_required = "authentik_policies.add_policybinding" |  | ||||||
|     form_class = PolicyBindingForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") |  | ||||||
|     success_message = _("Successfully created PolicyBinding") |  | ||||||
|  |  | ||||||
|     def get_initial(self) -> dict[str, Any]: |  | ||||||
|         if "target" in self.request.GET: |  | ||||||
|             initial_target_pk = self.request.GET["target"] |  | ||||||
|             targets = PolicyBindingModel.objects.filter( |  | ||||||
|                 pk=initial_target_pk |  | ||||||
|             ).select_subclasses() |  | ||||||
|             if not targets.exists(): |  | ||||||
|                 return {} |  | ||||||
|             max_order = PolicyBinding.objects.filter(target=targets.first()).aggregate( |  | ||||||
|                 Max("order") |  | ||||||
|             )["order__max"] |  | ||||||
|             if not isinstance(max_order, int): |  | ||||||
|                 max_order = -1 |  | ||||||
|             return {"target": targets.first(), "order": max_order + 1} |  | ||||||
|         return super().get_initial() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyBindingUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update policybinding""" |  | ||||||
|  |  | ||||||
|     model = PolicyBinding |  | ||||||
|     permission_required = "authentik_policies.change_policybinding" |  | ||||||
|     form_class = PolicyBindingForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") |  | ||||||
|     success_message = _("Successfully updated PolicyBinding") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyBindingDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete policybinding""" |  | ||||||
|  |  | ||||||
|     model = PolicyBinding |  | ||||||
|     permission_required = "authentik_policies.delete_policybinding" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") |  | ||||||
|     success_message = _("Successfully deleted PolicyBinding") |  | ||||||
| @ -1,108 +0,0 @@ | |||||||
| """authentik PropertyMapping administration""" |  | ||||||
| from json import dumps |  | ||||||
| from typing import Any |  | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http import HttpResponse |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import FormView |  | ||||||
| from django.views.generic.detail import DetailView |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.forms.policies import PolicyTestForm |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ) |  | ||||||
| from authentik.core.models import PropertyMapping |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new PropertyMapping""" |  | ||||||
|  |  | ||||||
|     model = PropertyMapping |  | ||||||
|     permission_required = "authentik_core.add_propertymapping" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Property Mapping") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update property_mapping""" |  | ||||||
|  |  | ||||||
|     model = PropertyMapping |  | ||||||
|     permission_required = "authentik_core.change_propertymapping" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Property Mapping") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete property_mapping""" |  | ||||||
|  |  | ||||||
|     model = PropertyMapping |  | ||||||
|     permission_required = "authentik_core.delete_propertymapping" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Property Mapping") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingTestView( |  | ||||||
|     LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView |  | ||||||
| ): |  | ||||||
|     """View to test property mappings""" |  | ||||||
|  |  | ||||||
|     model = PropertyMapping |  | ||||||
|     form_class = PolicyTestForm |  | ||||||
|     permission_required = "authentik_core.view_propertymapping" |  | ||||||
|     template_name = "administration/property_mapping/test.html" |  | ||||||
|     object = None |  | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None) -> PropertyMapping: |  | ||||||
|         return ( |  | ||||||
|             PropertyMapping.objects.filter(pk=self.kwargs.get("pk")) |  | ||||||
|             .select_subclasses() |  | ||||||
|             .first() |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: |  | ||||||
|         kwargs["property_mapping"] = self.get_object() |  | ||||||
|         return super().get_context_data(**kwargs) |  | ||||||
|  |  | ||||||
|     def post(self, *args, **kwargs) -> HttpResponse: |  | ||||||
|         self.object = self.get_object() |  | ||||||
|         return super().post(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def form_valid(self, form: PolicyTestForm) -> HttpResponse: |  | ||||||
|         mapping = self.get_object() |  | ||||||
|         user = form.cleaned_data.get("user") |  | ||||||
|  |  | ||||||
|         context = self.get_context_data(form=form) |  | ||||||
|         try: |  | ||||||
|             result = mapping.evaluate( |  | ||||||
|                 user, self.request, **form.cleaned_data.get("context", {}) |  | ||||||
|             ) |  | ||||||
|             context["result"] = dumps(result, indent=4) |  | ||||||
|         except Exception as exc:  # pylint: disable=broad-except |  | ||||||
|             context["result"] = str(exc) |  | ||||||
|         return self.render_to_response(context) |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| """authentik Provider administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ) |  | ||||||
| from authentik.core.models import Provider |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProviderCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new Provider""" |  | ||||||
|  |  | ||||||
|     model = Provider |  | ||||||
|     permission_required = "authentik_core.add_provider" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Provider") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProviderUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update provider""" |  | ||||||
|  |  | ||||||
|     model = Provider |  | ||||||
|     permission_required = "authentik_core.change_provider" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Provider") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProviderDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete provider""" |  | ||||||
|  |  | ||||||
|     model = Provider |  | ||||||
|     permission_required = "authentik_core.delete_provider" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Provider") |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| """authentik Source administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from guardian.mixins import PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ) |  | ||||||
| from authentik.core.models import Source |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new Source""" |  | ||||||
|  |  | ||||||
|     model = Source |  | ||||||
|     permission_required = "authentik_core.add_source" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_message = _("Successfully created Source") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update source""" |  | ||||||
|  |  | ||||||
|     model = Source |  | ||||||
|     permission_required = "authentik_core.change_source" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_message = _("Successfully updated Source") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete source""" |  | ||||||
|  |  | ||||||
|     model = Source |  | ||||||
|     permission_required = "authentik_core.delete_source" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_message = _("Successfully deleted Source") |  | ||||||
| @ -1,79 +0,0 @@ | |||||||
| """authentik Stage administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     InheritanceCreateView, |  | ||||||
|     InheritanceListView, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.flows.models import Stage |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     InheritanceListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all stages""" |  | ||||||
|  |  | ||||||
|     model = Stage |  | ||||||
|     template_name = "administration/stage/list.html" |  | ||||||
|     permission_required = "authentik_flows.view_stage" |  | ||||||
|     ordering = "name" |  | ||||||
|     search_fields = ["name"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     InheritanceCreateView, |  | ||||||
| ): |  | ||||||
|     """Create new Stage""" |  | ||||||
|  |  | ||||||
|     model = Stage |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     permission_required = "authentik_flows.add_stage" |  | ||||||
|  |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stages") |  | ||||||
|     success_message = _("Successfully created Stage") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     InheritanceUpdateView, |  | ||||||
| ): |  | ||||||
|     """Update stage""" |  | ||||||
|  |  | ||||||
|     model = Stage |  | ||||||
|     permission_required = "authentik_flows.update_application" |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stages") |  | ||||||
|     success_message = _("Successfully updated Stage") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete stage""" |  | ||||||
|  |  | ||||||
|     model = Stage |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     permission_required = "authentik_flows.delete_stage" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stages") |  | ||||||
|     success_message = _("Successfully deleted Stage") |  | ||||||
| @ -1,96 +0,0 @@ | |||||||
| """authentik StageBinding administration""" |  | ||||||
| from typing import Any |  | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.db.models import Max |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView, UpdateView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.flows.forms import FlowStageBindingForm |  | ||||||
| from authentik.flows.models import Flow, FlowStageBinding |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageBindingListView( |  | ||||||
|     LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView |  | ||||||
| ): |  | ||||||
|     """Show list of all flows""" |  | ||||||
|  |  | ||||||
|     model = FlowStageBinding |  | ||||||
|     permission_required = "authentik_flows.view_flowstagebinding" |  | ||||||
|     ordering = ["target", "order"] |  | ||||||
|     template_name = "administration/stage_binding/list.html" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageBindingCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new StageBinding""" |  | ||||||
|  |  | ||||||
|     model = FlowStageBinding |  | ||||||
|     permission_required = "authentik_flows.add_flowstagebinding" |  | ||||||
|     form_class = FlowStageBindingForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") |  | ||||||
|     success_message = _("Successfully created StageBinding") |  | ||||||
|  |  | ||||||
|     def get_initial(self) -> dict[str, Any]: |  | ||||||
|         if "target" in self.request.GET: |  | ||||||
|             initial_target_pk = self.request.GET["target"] |  | ||||||
|             targets = Flow.objects.filter(pk=initial_target_pk).select_subclasses() |  | ||||||
|             if not targets.exists(): |  | ||||||
|                 return {} |  | ||||||
|             max_order = FlowStageBinding.objects.filter( |  | ||||||
|                 target=targets.first() |  | ||||||
|             ).aggregate(Max("order"))["order__max"] |  | ||||||
|             if not isinstance(max_order, int): |  | ||||||
|                 max_order = -1 |  | ||||||
|             return {"target": targets.first(), "order": max_order + 1} |  | ||||||
|         return super().get_initial() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageBindingUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update FlowStageBinding""" |  | ||||||
|  |  | ||||||
|     model = FlowStageBinding |  | ||||||
|     permission_required = "authentik_flows.change_flowstagebinding" |  | ||||||
|     form_class = FlowStageBindingForm |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") |  | ||||||
|     success_message = _("Successfully updated StageBinding") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StageBindingDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete FlowStageBinding""" |  | ||||||
|  |  | ||||||
|     model = FlowStageBinding |  | ||||||
|     permission_required = "authentik_flows.delete_flowstagebinding" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") |  | ||||||
|     success_message = _("Successfully deleted FlowStageBinding") |  | ||||||
| @ -1,74 +0,0 @@ | |||||||
| """authentik Invitation administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http import HttpResponseRedirect |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
| from authentik.stages.invitation.forms import InvitationForm |  | ||||||
| from authentik.stages.invitation.models import Invitation |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvitationListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all invitations""" |  | ||||||
|  |  | ||||||
|     model = Invitation |  | ||||||
|     permission_required = "authentik_stages_invitation.view_invitation" |  | ||||||
|     template_name = "administration/stage_invitation/list.html" |  | ||||||
|     ordering = "-expires" |  | ||||||
|     search_fields = ["created_by__username", "expires", "fixed_data"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvitationCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Invitation""" |  | ||||||
|  |  | ||||||
|     model = Invitation |  | ||||||
|     form_class = InvitationForm |  | ||||||
|     permission_required = "authentik_stages_invitation.add_invitation" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-invitations") |  | ||||||
|     success_message = _("Successfully created Invitation") |  | ||||||
|  |  | ||||||
|     def form_valid(self, form): |  | ||||||
|         obj = form.save(commit=False) |  | ||||||
|         obj.created_by = self.request.user |  | ||||||
|         obj.save() |  | ||||||
|         return HttpResponseRedirect(self.success_url) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvitationDeleteView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Delete invitation""" |  | ||||||
|  |  | ||||||
|     model = Invitation |  | ||||||
|     permission_required = "authentik_stages_invitation.delete_invitation" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-invitations") |  | ||||||
|     success_message = _("Successfully deleted Invitation") |  | ||||||
| @ -1,88 +0,0 @@ | |||||||
| """authentik Prompt administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView, UpdateView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
| from authentik.stages.prompt.forms import PromptAdminForm |  | ||||||
| from authentik.stages.prompt.models import Prompt |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PromptListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all prompts""" |  | ||||||
|  |  | ||||||
|     model = Prompt |  | ||||||
|     permission_required = "authentik_stages_prompt.view_prompt" |  | ||||||
|     ordering = "order" |  | ||||||
|     template_name = "administration/stage_prompt/list.html" |  | ||||||
|     search_fields = [ |  | ||||||
|         "field_key", |  | ||||||
|         "label", |  | ||||||
|         "type", |  | ||||||
|         "placeholder", |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PromptCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create new Prompt""" |  | ||||||
|  |  | ||||||
|     model = Prompt |  | ||||||
|     form_class = PromptAdminForm |  | ||||||
|     permission_required = "authentik_stages_prompt.add_prompt" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") |  | ||||||
|     success_message = _("Successfully created Prompt") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PromptUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update prompt""" |  | ||||||
|  |  | ||||||
|     model = Prompt |  | ||||||
|     form_class = PromptAdminForm |  | ||||||
|     permission_required = "authentik_stages_prompt.change_prompt" |  | ||||||
|  |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") |  | ||||||
|     success_message = _("Successfully updated Prompt") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete prompt""" |  | ||||||
|  |  | ||||||
|     model = Prompt |  | ||||||
|     permission_required = "authentik_stages_prompt.delete_prompt" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") |  | ||||||
|     success_message = _("Successfully deleted Prompt") |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| """authentik Tasks List""" |  | ||||||
| from typing import Any, Dict |  | ||||||
|  |  | ||||||
| from django.views.generic.base import TemplateView |  | ||||||
|  |  | ||||||
| from authentik.admin.mixins import AdminRequiredMixin |  | ||||||
| from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskListView(AdminRequiredMixin, TemplateView): |  | ||||||
|     """Show list of all background tasks""" |  | ||||||
|  |  | ||||||
|     template_name = "administration/task/list.html" |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: |  | ||||||
|         kwargs = super().get_context_data(**kwargs) |  | ||||||
|         kwargs["object_list"] = sorted( |  | ||||||
|             TaskInfo.all().values(), key=lambda x: x.task_name |  | ||||||
|         ) |  | ||||||
|         kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL |  | ||||||
|         kwargs["task_warning"] = TaskResultStatus.WARNING |  | ||||||
|         kwargs["task_error"] = TaskResultStatus.ERROR |  | ||||||
|         return kwargs |  | ||||||
| @ -1,45 +0,0 @@ | |||||||
| """authentik Token administration""" |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import ListView |  | ||||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin |  | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.core.models import Token |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all tokens""" |  | ||||||
|  |  | ||||||
|     model = Token |  | ||||||
|     permission_required = "authentik_core.view_token" |  | ||||||
|     ordering = "expires" |  | ||||||
|     template_name = "administration/token/list.html" |  | ||||||
|     search_fields = [ |  | ||||||
|         "identifier", |  | ||||||
|         "intent", |  | ||||||
|         "user__username", |  | ||||||
|         "description", |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete token""" |  | ||||||
|  |  | ||||||
|     model = Token |  | ||||||
|     permission_required = "authentik_core.delete_token" |  | ||||||
|  |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:tokens") |  | ||||||
|     success_message = _("Successfully deleted Token") |  | ||||||
| @ -1,168 +0,0 @@ | |||||||
| """authentik User administration""" |  | ||||||
| from django.contrib import messages |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin |  | ||||||
| from django.contrib.auth.mixins import ( |  | ||||||
|     PermissionRequiredMixin as DjangoPermissionRequiredMixin, |  | ||||||
| ) |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.http import HttpRequest, HttpResponse |  | ||||||
| from django.http.response import HttpResponseRedirect |  | ||||||
| from django.shortcuts import redirect |  | ||||||
| from django.urls import reverse, reverse_lazy |  | ||||||
| from django.utils.http import urlencode |  | ||||||
| from django.utils.translation import gettext as _ |  | ||||||
| from django.views.generic import DetailView, ListView, UpdateView |  | ||||||
| from guardian.mixins import ( |  | ||||||
|     PermissionListMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     get_anonymous_user, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| from authentik.admin.forms.users import UserForm |  | ||||||
| from authentik.admin.views.utils import ( |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
|     SearchListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
| ) |  | ||||||
| from authentik.core.models import Token, User |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserListView( |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionListMixin, |  | ||||||
|     UserPaginateListMixin, |  | ||||||
|     SearchListMixin, |  | ||||||
|     ListView, |  | ||||||
| ): |  | ||||||
|     """Show list of all users""" |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     permission_required = "authentik_core.view_user" |  | ||||||
|     ordering = "username" |  | ||||||
|     template_name = "administration/user/list.html" |  | ||||||
|     search_fields = ["username", "name", "attributes"] |  | ||||||
|  |  | ||||||
|     def get_queryset(self): |  | ||||||
|         return super().get_queryset().exclude(pk=get_anonymous_user().pk) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserCreateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     DjangoPermissionRequiredMixin, |  | ||||||
|     CreateAssignPermView, |  | ||||||
| ): |  | ||||||
|     """Create user""" |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     form_class = UserForm |  | ||||||
|     permission_required = "authentik_core.add_user" |  | ||||||
|  |  | ||||||
|     template_name = "generic/create.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:users") |  | ||||||
|     success_message = _("Successfully created User") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserUpdateView( |  | ||||||
|     SuccessMessageMixin, |  | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     LoginRequiredMixin, |  | ||||||
|     PermissionRequiredMixin, |  | ||||||
|     UpdateView, |  | ||||||
| ): |  | ||||||
|     """Update user""" |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     form_class = UserForm |  | ||||||
|     permission_required = "authentik_core.change_user" |  | ||||||
|  |  | ||||||
|     # By default the object's name is user which is used by other checks |  | ||||||
|     context_object_name = "object" |  | ||||||
|     template_name = "generic/update.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:users") |  | ||||||
|     success_message = _("Successfully updated User") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): |  | ||||||
|     """Delete user""" |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     permission_required = "authentik_core.delete_user" |  | ||||||
|  |  | ||||||
|     # By default the object's name is user which is used by other checks |  | ||||||
|     context_object_name = "object" |  | ||||||
|     template_name = "generic/delete.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:users") |  | ||||||
|     success_message = _("Successfully deleted User") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserDisableView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView |  | ||||||
| ): |  | ||||||
|     """Disable user""" |  | ||||||
|  |  | ||||||
|     object: User |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     permission_required = "authentik_core.update_user" |  | ||||||
|  |  | ||||||
|     # By default the object's name is user which is used by other checks |  | ||||||
|     context_object_name = "object" |  | ||||||
|     template_name = "administration/user/disable.html" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:users") |  | ||||||
|     success_message = _("Successfully disabled User") |  | ||||||
|  |  | ||||||
|     def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: |  | ||||||
|         self.object: User = self.get_object() |  | ||||||
|         success_url = self.get_success_url() |  | ||||||
|         self.object.is_active = False |  | ||||||
|         self.object.save() |  | ||||||
|         return HttpResponseRedirect(success_url) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserEnableView( |  | ||||||
|     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView |  | ||||||
| ): |  | ||||||
|     """Enable user""" |  | ||||||
|  |  | ||||||
|     object: User |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     permission_required = "authentik_core.update_user" |  | ||||||
|  |  | ||||||
|     # By default the object's name is user which is used by other checks |  | ||||||
|     context_object_name = "object" |  | ||||||
|     success_url = reverse_lazy("authentik_admin:users") |  | ||||||
|     success_message = _("Successfully enabled User") |  | ||||||
|  |  | ||||||
|     def get(self, request: HttpRequest, *args, **kwargs): |  | ||||||
|         self.object: User = self.get_object() |  | ||||||
|         success_url = self.get_success_url() |  | ||||||
|         self.object.is_active = True |  | ||||||
|         self.object.save() |  | ||||||
|         return HttpResponseRedirect(success_url) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): |  | ||||||
|     """Get Password reset link for user""" |  | ||||||
|  |  | ||||||
|     model = User |  | ||||||
|     permission_required = "authentik_core.reset_user_password" |  | ||||||
|  |  | ||||||
|     def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: |  | ||||||
|         """Create token for user and return link""" |  | ||||||
|         super().get(request, *args, **kwargs) |  | ||||||
|         token, __ = Token.objects.get_or_create( |  | ||||||
|             identifier="password-reset-temp", user=self.object |  | ||||||
|         ) |  | ||||||
|         querystring = urlencode({"token": token.key}) |  | ||||||
|         link = request.build_absolute_uri( |  | ||||||
|             reverse("authentik_flows:default-recovery") + f"?{querystring}" |  | ||||||
|         ) |  | ||||||
|         messages.success( |  | ||||||
|             request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link}) |  | ||||||
|         ) |  | ||||||
|         return redirect("authentik_admin:users") |  | ||||||
| @ -1,124 +0,0 @@ | |||||||
| """authentik admin util views""" |  | ||||||
| from typing import Any, Dict, List, Optional |  | ||||||
| from urllib.parse import urlparse |  | ||||||
|  |  | ||||||
| from django.contrib import messages |  | ||||||
| from django.contrib.messages.views import SuccessMessageMixin |  | ||||||
| from django.contrib.postgres.search import SearchQuery, SearchVector |  | ||||||
| from django.db.models import QuerySet |  | ||||||
| from django.http import Http404 |  | ||||||
| from django.http.request import HttpRequest |  | ||||||
| from django.views.generic import DeleteView, ListView, UpdateView |  | ||||||
| from django.views.generic.list import MultipleObjectMixin |  | ||||||
|  |  | ||||||
| from authentik.lib.utils.reflection import all_subclasses |  | ||||||
| from authentik.lib.views import CreateAssignPermView |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeleteMessageView(SuccessMessageMixin, DeleteView): |  | ||||||
|     """DeleteView which shows `self.success_message` on successful deletion""" |  | ||||||
|  |  | ||||||
|     def delete(self, request, *args, **kwargs): |  | ||||||
|         messages.success(self.request, self.success_message) |  | ||||||
|         return super().delete(request, *args, **kwargs) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InheritanceListView(ListView): |  | ||||||
|     """ListView for objects using InheritanceManager""" |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |  | ||||||
|         kwargs["types"] = {x.__name__: x for x in all_subclasses(self.model)} |  | ||||||
|         return super().get_context_data(**kwargs) |  | ||||||
|  |  | ||||||
|     def get_queryset(self): |  | ||||||
|         return super().get_queryset().select_subclasses() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SearchListMixin(MultipleObjectMixin): |  | ||||||
|     """Accept search query using `search` querystring parameter. Requires self.search_fields, |  | ||||||
|     a list of all fields to search. Can contain special lookups like __icontains""" |  | ||||||
|  |  | ||||||
|     search_fields: List[str] |  | ||||||
|  |  | ||||||
|     def get_queryset(self) -> QuerySet: |  | ||||||
|         queryset = super().get_queryset() |  | ||||||
|         if "search" in self.request.GET: |  | ||||||
|             raw_query = self.request.GET["search"] |  | ||||||
|             if raw_query == "": |  | ||||||
|                 # Empty query, don't search at all |  | ||||||
|                 return queryset |  | ||||||
|             search = SearchQuery(raw_query, search_type="websearch") |  | ||||||
|             return queryset.annotate(search=SearchVector(*self.search_fields)).filter( |  | ||||||
|                 search=search |  | ||||||
|             ) |  | ||||||
|         return queryset |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InheritanceCreateView(CreateAssignPermView): |  | ||||||
|     """CreateView for objects using InheritanceManager""" |  | ||||||
|  |  | ||||||
|     def get_form_class(self): |  | ||||||
|         provider_type = self.request.GET.get("type") |  | ||||||
|         try: |  | ||||||
|             model = next( |  | ||||||
|                 x for x in all_subclasses(self.model) if x.__name__ == provider_type |  | ||||||
|             ) |  | ||||||
|         except StopIteration as exc: |  | ||||||
|             raise Http404 from exc |  | ||||||
|         return model().form |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: |  | ||||||
|         kwargs = super().get_context_data(**kwargs) |  | ||||||
|         form_cls = self.get_form_class() |  | ||||||
|         if hasattr(form_cls, "template_name"): |  | ||||||
|             kwargs["base_template"] = form_cls.template_name |  | ||||||
|         return kwargs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InheritanceUpdateView(UpdateView): |  | ||||||
|     """UpdateView for objects using InheritanceManager""" |  | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: |  | ||||||
|         kwargs = super().get_context_data(**kwargs) |  | ||||||
|         form_cls = self.get_form_class() |  | ||||||
|         if hasattr(form_cls, "template_name"): |  | ||||||
|             kwargs["base_template"] = form_cls.template_name |  | ||||||
|         return kwargs |  | ||||||
|  |  | ||||||
|     def get_form_class(self): |  | ||||||
|         return self.get_object().form |  | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |  | ||||||
|         return ( |  | ||||||
|             self.model.objects.filter(pk=self.kwargs.get("pk")) |  | ||||||
|             .select_subclasses() |  | ||||||
|             .first() |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BackSuccessUrlMixin: |  | ||||||
|     """Checks if a relative URL has been given as ?back param, and redirect to it. Otherwise |  | ||||||
|     default to self.success_url.""" |  | ||||||
|  |  | ||||||
|     request: HttpRequest |  | ||||||
|  |  | ||||||
|     success_url: Optional[str] |  | ||||||
|  |  | ||||||
|     def get_success_url(self) -> str: |  | ||||||
|         """get_success_url from FormMixin""" |  | ||||||
|         back_param = self.request.GET.get("back") |  | ||||||
|         if back_param: |  | ||||||
|             if not bool(urlparse(back_param).netloc): |  | ||||||
|                 return back_param |  | ||||||
|         return str(self.success_url) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserPaginateListMixin: |  | ||||||
|     """Get paginate_by value from user's attributes, defaulting to 15""" |  | ||||||
|  |  | ||||||
|     request: HttpRequest |  | ||||||
|  |  | ||||||
|     # pylint: disable=unused-argument |  | ||||||
|     def get_paginate_by(self, queryset: QuerySet) -> int: |  | ||||||
|         """get_paginate_by Function of ListView""" |  | ||||||
|         return self.request.user.attributes.get("paginate_by", 15) |  | ||||||
| @ -1,7 +1,7 @@ | |||||||
| """API Authentication""" | """API Authentication""" | ||||||
| from base64 import b64decode | from base64 import b64decode | ||||||
| from binascii import Error | from binascii import Error | ||||||
| from typing import Any, Optional, Tuple, Union | from typing import Any, Optional, Union | ||||||
|  |  | ||||||
| from rest_framework.authentication import BaseAuthentication, get_authorization_header | from rest_framework.authentication import BaseAuthentication, get_authorization_header | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| @ -10,6 +10,7 @@ from structlog.stdlib import get_logger | |||||||
| from authentik.core.models import Token, TokenIntents, User | from authentik.core.models import Token, TokenIntents, User | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
|  | X_AUTHENTIK_PREVENT_BASIC_HEADER = "HTTP_X_AUTHENTIK_PREVENT_BASIC" | ||||||
|  |  | ||||||
|  |  | ||||||
| def token_from_header(raw_header: bytes) -> Optional[Token]: | def token_from_header(raw_header: bytes) -> Optional[Token]: | ||||||
| @ -44,7 +45,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]: | |||||||
| class AuthentikTokenAuthentication(BaseAuthentication): | class AuthentikTokenAuthentication(BaseAuthentication): | ||||||
|     """Token-based authentication using HTTP Basic authentication""" |     """Token-based authentication using HTTP Basic authentication""" | ||||||
|  |  | ||||||
|     def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]: |     def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: | ||||||
|         """Token-based authentication using HTTP Basic authentication""" |         """Token-based authentication using HTTP Basic authentication""" | ||||||
|         auth = get_authorization_header(request) |         auth = get_authorization_header(request) | ||||||
|  |  | ||||||
| @ -55,4 +56,6 @@ class AuthentikTokenAuthentication(BaseAuthentication): | |||||||
|         return (token.user, None) |         return (token.user, None) | ||||||
|  |  | ||||||
|     def authenticate_header(self, request: Request) -> str: |     def authenticate_header(self, request: Request) -> str: | ||||||
|  |         if X_AUTHENTIK_PREVENT_BASIC_HEADER in request._request.META: | ||||||
|  |             return "" | ||||||
|         return 'Basic realm="authentik"' |         return 'Basic realm="authentik"' | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								authentik/api/decorators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								authentik/api/decorators.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | """API Decorators""" | ||||||
|  | from functools import wraps | ||||||
|  | from typing import Callable, Optional | ||||||
|  |  | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from rest_framework.response import Response | ||||||
|  | from rest_framework.viewsets import ModelViewSet | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def permission_required( | ||||||
|  |     perm: Optional[str] = None, other_perms: Optional[list[str]] = None | ||||||
|  | ): | ||||||
|  |     """Check permissions for a single custom action""" | ||||||
|  |  | ||||||
|  |     def wrapper_outter(func: Callable): | ||||||
|  |         """Check permissions for a single custom action""" | ||||||
|  |  | ||||||
|  |         @wraps(func) | ||||||
|  |         def wrapper(self: ModelViewSet, request: Request, *args, **kwargs) -> Response: | ||||||
|  |             if perm: | ||||||
|  |                 obj = self.get_object() | ||||||
|  |                 if not request.user.has_perm(perm, obj): | ||||||
|  |                     return self.permission_denied(request) | ||||||
|  |             if other_perms: | ||||||
|  |                 for other_perm in other_perms: | ||||||
|  |                     if not request.user.has_perm(other_perm): | ||||||
|  |                         return self.permission_denied(request) | ||||||
|  |             return func(self, request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |         return wrapper | ||||||
|  |  | ||||||
|  |     return wrapper_outter | ||||||
| @ -6,6 +6,7 @@ from rest_framework.response import Response | |||||||
| class Pagination(pagination.PageNumberPagination): | class Pagination(pagination.PageNumberPagination): | ||||||
|     """Pagination which includes total pages and current page""" |     """Pagination which includes total pages and current page""" | ||||||
|  |  | ||||||
|  |     page_query_param = "page" | ||||||
|     page_size_query_param = "page_size" |     page_size_query_param = "page_size" | ||||||
|  |  | ||||||
|     def get_paginated_response(self, data): |     def get_paginated_response(self, data): | ||||||
|  | |||||||
							
								
								
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | """Swagger Pagination Schema class""" | ||||||
|  | from typing import OrderedDict | ||||||
|  |  | ||||||
|  | from drf_yasg import openapi | ||||||
|  | from drf_yasg.inspectors import PaginatorInspector | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PaginationInspector(PaginatorInspector): | ||||||
|  |     """Swagger Pagination Schema class""" | ||||||
|  |  | ||||||
|  |     def get_paginated_response(self, paginator, response_schema): | ||||||
|  |         """ | ||||||
|  |         :param BasePagination paginator: the paginator | ||||||
|  |         :param openapi.Schema response_schema: the response schema that must be paged. | ||||||
|  |         :rtype: openapi.Schema | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         return openapi.Schema( | ||||||
|  |             type=openapi.TYPE_OBJECT, | ||||||
|  |             properties=OrderedDict( | ||||||
|  |                 ( | ||||||
|  |                     ( | ||||||
|  |                         "pagination", | ||||||
|  |                         openapi.Schema( | ||||||
|  |                             type=openapi.TYPE_OBJECT, | ||||||
|  |                             properties=OrderedDict( | ||||||
|  |                                 ( | ||||||
|  |                                     ("next", openapi.Schema(type=openapi.TYPE_NUMBER)), | ||||||
|  |                                     ( | ||||||
|  |                                         "previous", | ||||||
|  |                                         openapi.Schema(type=openapi.TYPE_NUMBER), | ||||||
|  |                                     ), | ||||||
|  |                                     ("count", openapi.Schema(type=openapi.TYPE_NUMBER)), | ||||||
|  |                                     ( | ||||||
|  |                                         "current", | ||||||
|  |                                         openapi.Schema(type=openapi.TYPE_NUMBER), | ||||||
|  |                                     ), | ||||||
|  |                                     ( | ||||||
|  |                                         "total_pages", | ||||||
|  |                                         openapi.Schema(type=openapi.TYPE_NUMBER), | ||||||
|  |                                     ), | ||||||
|  |                                     ( | ||||||
|  |                                         "start_index", | ||||||
|  |                                         openapi.Schema(type=openapi.TYPE_NUMBER), | ||||||
|  |                                     ), | ||||||
|  |                                     ( | ||||||
|  |                                         "end_index", | ||||||
|  |                                         openapi.Schema(type=openapi.TYPE_NUMBER), | ||||||
|  |                                     ), | ||||||
|  |                                 ) | ||||||
|  |                             ), | ||||||
|  |                             required=[ | ||||||
|  |                                 "next", | ||||||
|  |                                 "previous", | ||||||
|  |                                 "count", | ||||||
|  |                                 "current", | ||||||
|  |                                 "total_pages", | ||||||
|  |                                 "start_index", | ||||||
|  |                                 "end_index", | ||||||
|  |                             ], | ||||||
|  |                         ), | ||||||
|  |                     ), | ||||||
|  |                     ("results", response_schema), | ||||||
|  |                 ) | ||||||
|  |             ), | ||||||
|  |             required=["results", "pagination"], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_paginator_parameters(self, paginator): | ||||||
|  |         """ | ||||||
|  |         Get the pagination parameters for a single paginator **instance**. | ||||||
|  |  | ||||||
|  |         Should return :data:`.NotHandled` if this inspector | ||||||
|  |         does not know how to handle the given `paginator`. | ||||||
|  |  | ||||||
|  |         :param BasePagination paginator: the paginator | ||||||
|  |         :rtype: list[openapi.Parameter] | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         return [ | ||||||
|  |             openapi.Parameter( | ||||||
|  |                 "page", | ||||||
|  |                 openapi.IN_QUERY, | ||||||
|  |                 "Page Index", | ||||||
|  |                 False, | ||||||
|  |                 None, | ||||||
|  |                 openapi.TYPE_INTEGER, | ||||||
|  |             ), | ||||||
|  |             openapi.Parameter( | ||||||
|  |                 "page_size", | ||||||
|  |                 openapi.IN_QUERY, | ||||||
|  |                 "Page Size", | ||||||
|  |                 False, | ||||||
|  |                 None, | ||||||
|  |                 openapi.TYPE_INTEGER, | ||||||
|  |             ), | ||||||
|  |         ] | ||||||
							
								
								
									
										102
									
								
								authentik/api/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								authentik/api/schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | """Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224""" | ||||||
|  | from drf_yasg import openapi | ||||||
|  | from drf_yasg.inspectors.view import SwaggerAutoSchema | ||||||
|  | from drf_yasg.utils import force_real_str, is_list_view | ||||||
|  | from rest_framework import exceptions, status | ||||||
|  | from rest_framework.settings import api_settings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ErrorResponseAutoSchema(SwaggerAutoSchema): | ||||||
|  |     """Inspector which includes an error schema""" | ||||||
|  |  | ||||||
|  |     def get_generic_error_schema(self): | ||||||
|  |         """Get a generic error schema""" | ||||||
|  |         return openapi.Schema( | ||||||
|  |             "Generic API Error", | ||||||
|  |             type=openapi.TYPE_OBJECT, | ||||||
|  |             properties={ | ||||||
|  |                 "detail": openapi.Schema( | ||||||
|  |                     type=openapi.TYPE_STRING, description="Error details" | ||||||
|  |                 ), | ||||||
|  |                 "code": openapi.Schema( | ||||||
|  |                     type=openapi.TYPE_STRING, description="Error code" | ||||||
|  |                 ), | ||||||
|  |             }, | ||||||
|  |             required=["detail"], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_validation_error_schema(self): | ||||||
|  |         """Get a generic validation error schema""" | ||||||
|  |         return openapi.Schema( | ||||||
|  |             "Validation Error", | ||||||
|  |             type=openapi.TYPE_OBJECT, | ||||||
|  |             properties={ | ||||||
|  |                 api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema( | ||||||
|  |                     description="List of validation errors not related to any field", | ||||||
|  |                     type=openapi.TYPE_ARRAY, | ||||||
|  |                     items=openapi.Schema(type=openapi.TYPE_STRING), | ||||||
|  |                 ), | ||||||
|  |             }, | ||||||
|  |             additional_properties=openapi.Schema( | ||||||
|  |                 description=( | ||||||
|  |                     "A list of error messages for each " | ||||||
|  |                     "field that triggered a validation error" | ||||||
|  |                 ), | ||||||
|  |                 type=openapi.TYPE_ARRAY, | ||||||
|  |                 items=openapi.Schema(type=openapi.TYPE_STRING), | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_response_serializers(self): | ||||||
|  |         responses = super().get_response_serializers() | ||||||
|  |         definitions = self.components.with_scope( | ||||||
|  |             openapi.SCHEMA_DEFINITIONS | ||||||
|  |         )  # type: openapi.ReferenceResolver | ||||||
|  |  | ||||||
|  |         definitions.setdefault("GenericError", self.get_generic_error_schema) | ||||||
|  |         definitions.setdefault("ValidationError", self.get_validation_error_schema) | ||||||
|  |         definitions.setdefault("APIException", self.get_generic_error_schema) | ||||||
|  |  | ||||||
|  |         if self.get_request_serializer() or self.get_query_serializer(): | ||||||
|  |             responses.setdefault( | ||||||
|  |                 exceptions.ValidationError.status_code, | ||||||
|  |                 openapi.Response( | ||||||
|  |                     description=force_real_str( | ||||||
|  |                         exceptions.ValidationError.default_detail | ||||||
|  |                     ), | ||||||
|  |                     schema=openapi.SchemaRef(definitions, "ValidationError"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         security = self.get_security() | ||||||
|  |         if security is None or len(security) > 0: | ||||||
|  |             # Note: 401 error codes are coerced  into 403 see | ||||||
|  |             # rest_framework/views.py:433:handle_exception | ||||||
|  |             # This is b/c the API uses token auth which doesn't have WWW-Authenticate header | ||||||
|  |             responses.setdefault( | ||||||
|  |                 status.HTTP_403_FORBIDDEN, | ||||||
|  |                 openapi.Response( | ||||||
|  |                     description="Authentication credentials were invalid, absent or insufficient.", | ||||||
|  |                     schema=openapi.SchemaRef(definitions, "GenericError"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |         if not is_list_view(self.path, self.method, self.view): | ||||||
|  |             responses.setdefault( | ||||||
|  |                 exceptions.PermissionDenied.status_code, | ||||||
|  |                 openapi.Response( | ||||||
|  |                     description="Permission denied.", | ||||||
|  |                     schema=openapi.SchemaRef(definitions, "APIException"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |             responses.setdefault( | ||||||
|  |                 exceptions.NotFound.status_code, | ||||||
|  |                 openapi.Response( | ||||||
|  |                     description=( | ||||||
|  |                         "Object does not exist or caller " | ||||||
|  |                         "has insufficient permissions to access it." | ||||||
|  |                     ), | ||||||
|  |                     schema=openapi.SchemaRef(definitions, "APIException"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         return responses | ||||||
							
								
								
									
										26
									
								
								authentik/api/templates/api/swagger.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								authentik/api/templates/api/swagger.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | {% extends "base/skeleton.html" %} | ||||||
|  |  | ||||||
|  | {% load static %} | ||||||
|  |  | ||||||
|  | {% block title %} | ||||||
|  | authentik API Browser | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block head %} | ||||||
|  | <script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | <rapi-doc | ||||||
|  |     spec-url="{{ path }}" | ||||||
|  |     heading-text="authentik" | ||||||
|  |     theme="dark" | ||||||
|  |     render-style="view" | ||||||
|  |     primary-color="#fd4b2d" | ||||||
|  |     allow-spec-url-load="false" | ||||||
|  |     allow-spec-file-load="false"> | ||||||
|  |     <div slot="logo"> | ||||||
|  |         <img src="{% static 'dist/assets/icons/icon.png' %}" style="width:50px; height:50px" /> | ||||||
|  |     </div> | ||||||
|  | </rapi-doc> | ||||||
|  | {% endblock %} | ||||||
| @ -1,31 +0,0 @@ | |||||||
| {% extends "rest_framework/base.html" %} |  | ||||||
|  |  | ||||||
| {% block title %}{% if name %}{{ name }} – {% endif %}authentik{% endblock %} |  | ||||||
|  |  | ||||||
| {% block branding %} |  | ||||||
| <span class='navbar-brand'> |  | ||||||
|     authentik |  | ||||||
| </span> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block style %} |  | ||||||
| {{ block.super }} |  | ||||||
| <style> |  | ||||||
|     body { |  | ||||||
|         background-color: #18191a; |  | ||||||
|         color: #fafafa; |  | ||||||
|     } |  | ||||||
|     .prettyprint { |  | ||||||
|         background-color: #1c1e21; |  | ||||||
|         color: #fafafa; |  | ||||||
|         border: 1px solid #2b2e33; |  | ||||||
|     } |  | ||||||
|     .pln { |  | ||||||
|         color: #fafafa; |  | ||||||
|     } |  | ||||||
|     .well { |  | ||||||
|         background-color: #1c1e21; |  | ||||||
|         border: 1px solid #2b2e33; |  | ||||||
|     } |  | ||||||
| </style> |  | ||||||
| {% endblock %} |  | ||||||
							
								
								
									
										24
									
								
								authentik/api/tests/test_swagger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								authentik/api/tests/test_swagger.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | """Swagger generation tests""" | ||||||
|  | from json import loads | ||||||
|  |  | ||||||
|  | from django.urls import reverse | ||||||
|  | from rest_framework.test import APITestCase | ||||||
|  | from yaml import safe_load | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestSwaggerGeneration(APITestCase): | ||||||
|  |     """Generic admin tests""" | ||||||
|  |  | ||||||
|  |     def test_yaml(self): | ||||||
|  |         """Test YAML generation""" | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:schema-json", kwargs={"format": ".yaml"}), | ||||||
|  |         ) | ||||||
|  |         self.assertTrue(safe_load(response.content.decode())) | ||||||
|  |  | ||||||
|  |     def test_json(self): | ||||||
|  |         """Test JSON generation""" | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:schema-json", kwargs={"format": ".json"}), | ||||||
|  |         ) | ||||||
|  |         self.assertTrue(loads(response.content.decode())) | ||||||
| @ -1,30 +1,32 @@ | |||||||
| """core Configs API""" | """core Configs API""" | ||||||
| from django.db.models import Model | from drf_yasg.utils import swagger_auto_schema | ||||||
| from drf_yasg2.utils import swagger_auto_schema | from rest_framework.fields import BooleanField, CharField, ListField | ||||||
| from rest_framework.permissions import AllowAny | from rest_framework.permissions import AllowAny | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ReadOnlyField, Serializer |  | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
|  | from authentik.core.api.utils import PassiveSerializer | ||||||
| from authentik.lib.config import CONFIG | from authentik.lib.config import CONFIG | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConfigSerializer(Serializer): | class FooterLinkSerializer(PassiveSerializer): | ||||||
|  |     """Links returned in Config API""" | ||||||
|  |  | ||||||
|  |     href = CharField(read_only=True) | ||||||
|  |     name = CharField(read_only=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConfigSerializer(PassiveSerializer): | ||||||
|     """Serialize authentik Config into DRF Object""" |     """Serialize authentik Config into DRF Object""" | ||||||
|  |  | ||||||
|     branding_logo = ReadOnlyField() |     branding_logo = CharField(read_only=True) | ||||||
|     branding_title = ReadOnlyField() |     branding_title = CharField(read_only=True) | ||||||
|  |     ui_footer_links = ListField(child=FooterLinkSerializer(), read_only=True) | ||||||
|  |  | ||||||
|     error_reporting_enabled = ReadOnlyField() |     error_reporting_enabled = BooleanField(read_only=True) | ||||||
|     error_reporting_environment = ReadOnlyField() |     error_reporting_environment = CharField(read_only=True) | ||||||
|     error_reporting_send_pii = ReadOnlyField() |     error_reporting_send_pii = BooleanField(read_only=True) | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConfigsViewSet(ViewSet): | class ConfigsViewSet(ViewSet): | ||||||
| @ -32,7 +34,7 @@ class ConfigsViewSet(ViewSet): | |||||||
|  |  | ||||||
|     permission_classes = [AllowAny] |     permission_classes = [AllowAny] | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: ConfigSerializer(many=True)}) |     @swagger_auto_schema(responses={200: ConfigSerializer(many=False)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """Retrive public configuration options""" |         """Retrive public configuration options""" | ||||||
|         config = ConfigSerializer( |         config = ConfigSerializer( | ||||||
| @ -42,6 +44,7 @@ class ConfigsViewSet(ViewSet): | |||||||
|                 "error_reporting_enabled": CONFIG.y("error_reporting.enabled"), |                 "error_reporting_enabled": CONFIG.y("error_reporting.enabled"), | ||||||
|                 "error_reporting_environment": CONFIG.y("error_reporting.environment"), |                 "error_reporting_environment": CONFIG.y("error_reporting.environment"), | ||||||
|                 "error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"), |                 "error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"), | ||||||
|  |                 "ui_footer_links": CONFIG.y("authentik.footer_links"), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         return Response(config.data) |         return Response(config.data) | ||||||
|  | |||||||
| @ -1,37 +0,0 @@ | |||||||
| """core messages API""" |  | ||||||
| from django.contrib.messages import get_messages |  | ||||||
| from django.db.models import Model |  | ||||||
| from drf_yasg2.utils import swagger_auto_schema |  | ||||||
| from rest_framework.permissions import AllowAny |  | ||||||
| from rest_framework.request import Request |  | ||||||
| from rest_framework.response import Response |  | ||||||
| from rest_framework.serializers import ReadOnlyField, Serializer |  | ||||||
| from rest_framework.viewsets import ViewSet |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MessageSerializer(Serializer): |  | ||||||
|     """Serialize Django Message into DRF Object""" |  | ||||||
|  |  | ||||||
|     message = ReadOnlyField() |  | ||||||
|     level = ReadOnlyField() |  | ||||||
|     tags = ReadOnlyField() |  | ||||||
|     extra_tags = ReadOnlyField() |  | ||||||
|     level_tag = ReadOnlyField() |  | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MessagesViewSet(ViewSet): |  | ||||||
|     """Read-only view set that returns the current session's messages""" |  | ||||||
|  |  | ||||||
|     permission_classes = [AllowAny] |  | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: MessageSerializer(many=True)}) |  | ||||||
|     def list(self, request: Request) -> Response: |  | ||||||
|         """List current messages and pass into Serializer""" |  | ||||||
|         all_messages = list(get_messages(request)) |  | ||||||
|         return Response(MessageSerializer(all_messages, many=True).data) |  | ||||||
| @ -1,16 +1,17 @@ | |||||||
| """api v2 urls""" | """api v2 urls""" | ||||||
| from django.urls import path, re_path | from django.urls import path, re_path | ||||||
| from drf_yasg2 import openapi | from drf_yasg import openapi | ||||||
| from drf_yasg2.views import get_schema_view | from drf_yasg.views import get_schema_view | ||||||
| from rest_framework import routers | from rest_framework import routers | ||||||
| from rest_framework.permissions import AllowAny | from rest_framework.permissions import AllowAny | ||||||
|  |  | ||||||
|  | from authentik.admin.api.meta import AppsViewSet | ||||||
| from authentik.admin.api.metrics import AdministrationMetricsViewSet | from authentik.admin.api.metrics import AdministrationMetricsViewSet | ||||||
| from authentik.admin.api.tasks import TaskViewSet | from authentik.admin.api.tasks import TaskViewSet | ||||||
| from authentik.admin.api.version import VersionViewSet | from authentik.admin.api.version import VersionViewSet | ||||||
| from authentik.admin.api.workers import WorkerViewSet | from authentik.admin.api.workers import WorkerViewSet | ||||||
| from authentik.api.v2.config import ConfigsViewSet | from authentik.api.v2.config import ConfigsViewSet | ||||||
| from authentik.api.v2.messages import MessagesViewSet | from authentik.api.views import SwaggerView | ||||||
| from authentik.core.api.applications import ApplicationViewSet | from authentik.core.api.applications import ApplicationViewSet | ||||||
| from authentik.core.api.groups import GroupViewSet | from authentik.core.api.groups import GroupViewSet | ||||||
| from authentik.core.api.propertymappings import PropertyMappingViewSet | from authentik.core.api.propertymappings import PropertyMappingViewSet | ||||||
| @ -23,49 +24,71 @@ from authentik.events.api.event import EventViewSet | |||||||
| from authentik.events.api.notification import NotificationViewSet | from authentik.events.api.notification import NotificationViewSet | ||||||
| from authentik.events.api.notification_rule import NotificationRuleViewSet | from authentik.events.api.notification_rule import NotificationRuleViewSet | ||||||
| from authentik.events.api.notification_transport import NotificationTransportViewSet | from authentik.events.api.notification_transport import NotificationTransportViewSet | ||||||
| from authentik.flows.api import ( | from authentik.flows.api.bindings import FlowStageBindingViewSet | ||||||
|     FlowCacheViewSet, | from authentik.flows.api.flows import FlowViewSet | ||||||
|     FlowStageBindingViewSet, | from authentik.flows.api.stages import StageViewSet | ||||||
|     FlowViewSet, | from authentik.flows.views import FlowExecutorView | ||||||
|     StageViewSet, |  | ||||||
| ) |  | ||||||
| from authentik.outposts.api.outpost_service_connections import ( | from authentik.outposts.api.outpost_service_connections import ( | ||||||
|     DockerServiceConnectionViewSet, |     DockerServiceConnectionViewSet, | ||||||
|     KubernetesServiceConnectionViewSet, |     KubernetesServiceConnectionViewSet, | ||||||
|     ServiceConnectionViewSet, |     ServiceConnectionViewSet, | ||||||
| ) | ) | ||||||
| from authentik.outposts.api.outposts import OutpostViewSet | from authentik.outposts.api.outposts import OutpostViewSet | ||||||
| from authentik.policies.api import ( | from authentik.policies.api.bindings import PolicyBindingViewSet | ||||||
|     PolicyBindingViewSet, | from authentik.policies.api.policies import PolicyViewSet | ||||||
|     PolicyCacheViewSet, |  | ||||||
|     PolicyViewSet, |  | ||||||
| ) |  | ||||||
| from authentik.policies.dummy.api import DummyPolicyViewSet | from authentik.policies.dummy.api import DummyPolicyViewSet | ||||||
| from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet | from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet | ||||||
| from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet | from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet | ||||||
| from authentik.policies.expression.api import ExpressionPolicyViewSet | from authentik.policies.expression.api import ExpressionPolicyViewSet | ||||||
| from authentik.policies.group_membership.api import GroupMembershipPolicyViewSet |  | ||||||
| from authentik.policies.hibp.api import HaveIBeenPwendPolicyViewSet | from authentik.policies.hibp.api import HaveIBeenPwendPolicyViewSet | ||||||
| from authentik.policies.password.api import PasswordPolicyViewSet | from authentik.policies.password.api import PasswordPolicyViewSet | ||||||
| from authentik.policies.reputation.api import ReputationPolicyViewSet | from authentik.policies.reputation.api import ( | ||||||
| from authentik.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet |     IPReputationViewSet, | ||||||
|  |     ReputationPolicyViewSet, | ||||||
|  |     UserReputationViewSet, | ||||||
|  | ) | ||||||
|  | from authentik.providers.oauth2.api.provider import OAuth2ProviderViewSet | ||||||
|  | from authentik.providers.oauth2.api.scope import ScopeMappingViewSet | ||||||
|  | from authentik.providers.oauth2.api.tokens import ( | ||||||
|  |     AuthorizationCodeViewSet, | ||||||
|  |     RefreshTokenViewSet, | ||||||
|  | ) | ||||||
| from authentik.providers.proxy.api import ( | from authentik.providers.proxy.api import ( | ||||||
|     ProxyOutpostConfigViewSet, |     ProxyOutpostConfigViewSet, | ||||||
|     ProxyProviderViewSet, |     ProxyProviderViewSet, | ||||||
| ) | ) | ||||||
| from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet | from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet | ||||||
| from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet | from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet | ||||||
| from authentik.sources.oauth.api import OAuthSourceViewSet | from authentik.sources.oauth.api.source import OAuthSourceViewSet | ||||||
|  | from authentik.sources.oauth.api.source_connection import ( | ||||||
|  |     UserOAuthSourceConnectionViewSet, | ||||||
|  | ) | ||||||
| from authentik.sources.saml.api import SAMLSourceViewSet | from authentik.sources.saml.api import SAMLSourceViewSet | ||||||
|  | from authentik.stages.authenticator_static.api import ( | ||||||
|  |     AuthenticatorStaticStageViewSet, | ||||||
|  |     StaticAdminDeviceViewSet, | ||||||
|  |     StaticDeviceViewSet, | ||||||
|  | ) | ||||||
|  | from authentik.stages.authenticator_totp.api import ( | ||||||
|  |     AuthenticatorTOTPStageViewSet, | ||||||
|  |     TOTPAdminDeviceViewSet, | ||||||
|  |     TOTPDeviceViewSet, | ||||||
|  | ) | ||||||
|  | from authentik.stages.authenticator_validate.api import ( | ||||||
|  |     AuthenticatorValidateStageViewSet, | ||||||
|  | ) | ||||||
|  | from authentik.stages.authenticator_webauthn.api import ( | ||||||
|  |     AuthenticateWebAuthnStageViewSet, | ||||||
|  |     WebAuthnAdminDeviceViewSet, | ||||||
|  |     WebAuthnDeviceViewSet, | ||||||
|  | ) | ||||||
| from authentik.stages.captcha.api import CaptchaStageViewSet | from authentik.stages.captcha.api import CaptchaStageViewSet | ||||||
| from authentik.stages.consent.api import ConsentStageViewSet | from authentik.stages.consent.api import ConsentStageViewSet, UserConsentViewSet | ||||||
|  | from authentik.stages.deny.api import DenyStageViewSet | ||||||
| from authentik.stages.dummy.api import DummyStageViewSet | from authentik.stages.dummy.api import DummyStageViewSet | ||||||
| from authentik.stages.email.api import EmailStageViewSet | from authentik.stages.email.api import EmailStageViewSet | ||||||
| from authentik.stages.identification.api import IdentificationStageViewSet | from authentik.stages.identification.api import IdentificationStageViewSet | ||||||
| from authentik.stages.invitation.api import InvitationStageViewSet, InvitationViewSet | from authentik.stages.invitation.api import InvitationStageViewSet, InvitationViewSet | ||||||
| from authentik.stages.otp_static.api import OTPStaticStageViewSet |  | ||||||
| from authentik.stages.otp_time.api import OTPTimeStageViewSet |  | ||||||
| from authentik.stages.otp_validate.api import OTPValidateStageViewSet |  | ||||||
| from authentik.stages.password.api import PasswordStageViewSet | from authentik.stages.password.api import PasswordStageViewSet | ||||||
| from authentik.stages.prompt.api import PromptStageViewSet, PromptViewSet | from authentik.stages.prompt.api import PromptStageViewSet, PromptViewSet | ||||||
| from authentik.stages.user_delete.api import UserDeleteStageViewSet | from authentik.stages.user_delete.api import UserDeleteStageViewSet | ||||||
| @ -75,17 +98,18 @@ from authentik.stages.user_write.api import UserWriteStageViewSet | |||||||
|  |  | ||||||
| router = routers.DefaultRouter() | router = routers.DefaultRouter() | ||||||
|  |  | ||||||
| router.register("root/messages", MessagesViewSet, basename="messages") |  | ||||||
| router.register("root/config", ConfigsViewSet, basename="configs") | router.register("root/config", ConfigsViewSet, basename="configs") | ||||||
|  |  | ||||||
| router.register("admin/version", VersionViewSet, basename="admin_version") | router.register("admin/version", VersionViewSet, basename="admin_version") | ||||||
| router.register("admin/workers", WorkerViewSet, basename="admin_workers") | router.register("admin/workers", WorkerViewSet, basename="admin_workers") | ||||||
| router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics") | router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics") | ||||||
| router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") | router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") | ||||||
|  | router.register("admin/apps", AppsViewSet, basename="apps") | ||||||
|  |  | ||||||
| router.register("core/applications", ApplicationViewSet) | router.register("core/applications", ApplicationViewSet) | ||||||
| router.register("core/groups", GroupViewSet) | router.register("core/groups", GroupViewSet) | ||||||
| router.register("core/users", UserViewSet) | router.register("core/users", UserViewSet) | ||||||
|  | router.register("core/user_consent", UserConsentViewSet) | ||||||
| router.register("core/tokens", TokenViewSet) | router.register("core/tokens", TokenViewSet) | ||||||
|  |  | ||||||
| router.register("outposts/outposts", OutpostViewSet) | router.register("outposts/outposts", OutpostViewSet) | ||||||
| @ -97,7 +121,6 @@ router.register( | |||||||
| router.register("outposts/proxy", ProxyOutpostConfigViewSet) | router.register("outposts/proxy", ProxyOutpostConfigViewSet) | ||||||
|  |  | ||||||
| router.register("flows/instances", FlowViewSet) | router.register("flows/instances", FlowViewSet) | ||||||
| router.register("flows/cached", FlowCacheViewSet, basename="flows_cache") |  | ||||||
| router.register("flows/bindings", FlowStageBindingViewSet) | router.register("flows/bindings", FlowStageBindingViewSet) | ||||||
|  |  | ||||||
| router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) | router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) | ||||||
| @ -108,19 +131,20 @@ router.register("events/transports", NotificationTransportViewSet) | |||||||
| router.register("events/rules", NotificationRuleViewSet) | router.register("events/rules", NotificationRuleViewSet) | ||||||
|  |  | ||||||
| router.register("sources/all", SourceViewSet) | router.register("sources/all", SourceViewSet) | ||||||
|  | router.register("sources/oauth_user_connections", UserOAuthSourceConnectionViewSet) | ||||||
| router.register("sources/ldap", LDAPSourceViewSet) | router.register("sources/ldap", LDAPSourceViewSet) | ||||||
| router.register("sources/saml", SAMLSourceViewSet) | router.register("sources/saml", SAMLSourceViewSet) | ||||||
| router.register("sources/oauth", OAuthSourceViewSet) | router.register("sources/oauth", OAuthSourceViewSet) | ||||||
|  |  | ||||||
| router.register("policies/all", PolicyViewSet) | router.register("policies/all", PolicyViewSet) | ||||||
| router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache") |  | ||||||
| router.register("policies/bindings", PolicyBindingViewSet) | router.register("policies/bindings", PolicyBindingViewSet) | ||||||
| router.register("policies/expression", ExpressionPolicyViewSet) | router.register("policies/expression", ExpressionPolicyViewSet) | ||||||
| router.register("policies/event_matcher", EventMatcherPolicyViewSet) | router.register("policies/event_matcher", EventMatcherPolicyViewSet) | ||||||
| router.register("policies/group_membership", GroupMembershipPolicyViewSet) |  | ||||||
| router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet) | router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet) | ||||||
| router.register("policies/password_expiry", PasswordExpiryPolicyViewSet) | router.register("policies/password_expiry", PasswordExpiryPolicyViewSet) | ||||||
| router.register("policies/password", PasswordPolicyViewSet) | router.register("policies/password", PasswordPolicyViewSet) | ||||||
|  | router.register("policies/reputation/users", UserReputationViewSet) | ||||||
|  | router.register("policies/reputation/ips", IPReputationViewSet) | ||||||
| router.register("policies/reputation", ReputationPolicyViewSet) | router.register("policies/reputation", ReputationPolicyViewSet) | ||||||
|  |  | ||||||
| router.register("providers/all", ProviderViewSet) | router.register("providers/all", ProviderViewSet) | ||||||
| @ -128,21 +152,33 @@ router.register("providers/proxy", ProxyProviderViewSet) | |||||||
| router.register("providers/oauth2", OAuth2ProviderViewSet) | router.register("providers/oauth2", OAuth2ProviderViewSet) | ||||||
| router.register("providers/saml", SAMLProviderViewSet) | router.register("providers/saml", SAMLProviderViewSet) | ||||||
|  |  | ||||||
|  | router.register("oauth2/authorization_codes", AuthorizationCodeViewSet) | ||||||
|  | router.register("oauth2/refresh_tokens", RefreshTokenViewSet) | ||||||
|  |  | ||||||
| router.register("propertymappings/all", PropertyMappingViewSet) | router.register("propertymappings/all", PropertyMappingViewSet) | ||||||
| router.register("propertymappings/ldap", LDAPPropertyMappingViewSet) | router.register("propertymappings/ldap", LDAPPropertyMappingViewSet) | ||||||
| router.register("propertymappings/saml", SAMLPropertyMappingViewSet) | router.register("propertymappings/saml", SAMLPropertyMappingViewSet) | ||||||
| router.register("propertymappings/scope", ScopeMappingViewSet) | router.register("propertymappings/scope", ScopeMappingViewSet) | ||||||
|  |  | ||||||
|  | router.register("authenticators/static", StaticDeviceViewSet) | ||||||
|  | router.register("authenticators/totp", TOTPDeviceViewSet) | ||||||
|  | router.register("authenticators/webauthn", WebAuthnDeviceViewSet) | ||||||
|  | router.register("authenticators/admin/static", StaticAdminDeviceViewSet) | ||||||
|  | router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet) | ||||||
|  | router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet) | ||||||
|  |  | ||||||
| router.register("stages/all", StageViewSet) | router.register("stages/all", StageViewSet) | ||||||
|  | router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet) | ||||||
|  | router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet) | ||||||
|  | router.register("stages/authenticator/validate", AuthenticatorValidateStageViewSet) | ||||||
|  | router.register("stages/authenticator/webauthn", AuthenticateWebAuthnStageViewSet) | ||||||
| router.register("stages/captcha", CaptchaStageViewSet) | router.register("stages/captcha", CaptchaStageViewSet) | ||||||
| router.register("stages/consent", ConsentStageViewSet) | router.register("stages/consent", ConsentStageViewSet) | ||||||
|  | router.register("stages/deny", DenyStageViewSet) | ||||||
| router.register("stages/email", EmailStageViewSet) | router.register("stages/email", EmailStageViewSet) | ||||||
| router.register("stages/identification", IdentificationStageViewSet) | router.register("stages/identification", IdentificationStageViewSet) | ||||||
| router.register("stages/invitation", InvitationStageViewSet) |  | ||||||
| router.register("stages/invitation/invitations", InvitationViewSet) | router.register("stages/invitation/invitations", InvitationViewSet) | ||||||
| router.register("stages/otp_static", OTPStaticStageViewSet) | router.register("stages/invitation/stages", InvitationStageViewSet) | ||||||
| router.register("stages/otp_time", OTPTimeStageViewSet) |  | ||||||
| router.register("stages/otp_validate", OTPValidateStageViewSet) |  | ||||||
| router.register("stages/password", PasswordStageViewSet) | router.register("stages/password", PasswordStageViewSet) | ||||||
| router.register("stages/prompt/prompts", PromptViewSet) | router.register("stages/prompt/prompts", PromptViewSet) | ||||||
| router.register("stages/prompt/stages", PromptStageViewSet) | router.register("stages/prompt/stages", PromptStageViewSet) | ||||||
| @ -156,28 +192,29 @@ router.register("policies/dummy", DummyPolicyViewSet) | |||||||
|  |  | ||||||
| info = openapi.Info( | info = openapi.Info( | ||||||
|     title="authentik API", |     title="authentik API", | ||||||
|     default_version="v2", |     default_version="v2beta", | ||||||
|     contact=openapi.Contact(email="hello@beryju.org"), |     contact=openapi.Contact(email="hello@beryju.org"), | ||||||
|     license=openapi.License( |     license=openapi.License( | ||||||
|         name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE" |         name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE" | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| SchemaView = get_schema_view( | SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,)) | ||||||
|     info, |  | ||||||
|     public=True, |  | ||||||
|     permission_classes=(AllowAny,), |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| urlpatterns = [ | urlpatterns = ( | ||||||
|     re_path( |     [ | ||||||
|         r"^swagger(?P<format>\.json|\.yaml)$", |         path("", SwaggerView.as_view(), name="swagger"), | ||||||
|         SchemaView.without_ui(cache_timeout=0), |     ] | ||||||
|         name="schema-json", |     + router.urls | ||||||
|     ), |     + [ | ||||||
|     path( |         path( | ||||||
|         "swagger/", |             "flows/executor/<slug:flow_slug>/", | ||||||
|         SchemaView.with_ui("swagger", cache_timeout=0), |             FlowExecutorView.as_view(), | ||||||
|         name="schema-swagger-ui", |             name="flow-executor", | ||||||
|     ), |         ), | ||||||
|     path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"), |         re_path( | ||||||
| ] + router.urls |             r"^swagger(?P<format>\.json|\.yaml)$", | ||||||
|  |             SchemaView.without_ui(cache_timeout=0), | ||||||
|  |             name="schema-json", | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								authentik/api/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								authentik/api/views.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | """General API Views""" | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | from django.urls import reverse | ||||||
|  | from django.views.generic import TemplateView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SwaggerView(TemplateView): | ||||||
|  |     """Show swagger view based on rapi-doc""" | ||||||
|  |  | ||||||
|  |     template_name = "api/swagger.html" | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||||
|  |         path = self.request.build_absolute_uri( | ||||||
|  |             reverse( | ||||||
|  |                 "authentik_api:schema-json", | ||||||
|  |                 kwargs={ | ||||||
|  |                     "format": ".json", | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         return super().get_context_data(path=path, **kwargs) | ||||||
| @ -1,20 +0,0 @@ | |||||||
| """authentik core admin""" |  | ||||||
|  |  | ||||||
| from django.apps import AppConfig, apps |  | ||||||
| from django.contrib import admin |  | ||||||
| from django.contrib.admin.sites import AlreadyRegistered |  | ||||||
| from guardian.admin import GuardedModelAdmin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def admin_autoregister(app: AppConfig): |  | ||||||
|     """Automatically register all models from app""" |  | ||||||
|     for model in app.get_models(): |  | ||||||
|         try: |  | ||||||
|             admin.site.register(model, GuardedModelAdmin) |  | ||||||
|         except AlreadyRegistered: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| for _app in apps.get_app_configs(): |  | ||||||
|     if _app.label.startswith("authentik_"): |  | ||||||
|         admin_autoregister(_app) |  | ||||||
| @ -1,11 +1,14 @@ | |||||||
| """Application API Views""" | """Application API Views""" | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.db.models import QuerySet | from django.db.models import QuerySet | ||||||
| from django.http.response import Http404 | from django.http.response import HttpResponseBadRequest | ||||||
| from guardian.shortcuts import get_objects_for_user | from drf_yasg import openapi | ||||||
|  | from drf_yasg.utils import no_body, swagger_auto_schema | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import SerializerMethodField | from rest_framework.fields import SerializerMethodField | ||||||
| from rest_framework.generics import get_object_or_404 | from rest_framework.parsers import MultiPartParser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ModelSerializer | from rest_framework.serializers import ModelSerializer | ||||||
| @ -13,7 +16,8 @@ from rest_framework.viewsets import ModelViewSet | |||||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.admin.api.metrics import get_events_per_1h | from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h | ||||||
|  | from authentik.api.decorators import permission_required | ||||||
| from authentik.core.api.providers import ProviderSerializer | from authentik.core.api.providers import ProviderSerializer | ||||||
| from authentik.core.models import Application | from authentik.core.models import Application | ||||||
| from authentik.events.models import EventAction | from authentik.events.models import EventAction | ||||||
| @ -31,11 +35,11 @@ class ApplicationSerializer(ModelSerializer): | |||||||
|     """Application Serializer""" |     """Application Serializer""" | ||||||
|  |  | ||||||
|     launch_url = SerializerMethodField() |     launch_url = SerializerMethodField() | ||||||
|     provider = ProviderSerializer(source="get_provider", required=False) |     provider_obj = ProviderSerializer(source="get_provider", required=False) | ||||||
|  |  | ||||||
|     def get_launch_url(self, instance: Application) -> str: |     def get_launch_url(self, instance: Application) -> Optional[str]: | ||||||
|         """Get generated launch URL""" |         """Get generated launch URL""" | ||||||
|         return instance.get_launch_url() or "" |         return instance.get_launch_url() | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
| @ -45,12 +49,13 @@ class ApplicationSerializer(ModelSerializer): | |||||||
|             "name", |             "name", | ||||||
|             "slug", |             "slug", | ||||||
|             "provider", |             "provider", | ||||||
|  |             "provider_obj", | ||||||
|             "launch_url", |             "launch_url", | ||||||
|             "meta_launch_url", |             "meta_launch_url", | ||||||
|             "meta_icon", |             "meta_icon", | ||||||
|             "meta_description", |             "meta_description", | ||||||
|             "meta_publisher", |             "meta_publisher", | ||||||
|             "policies", |             "policy_engine_mode", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -109,15 +114,46 @@ class ApplicationViewSet(ModelViewSet): | |||||||
|         serializer = self.get_serializer(allowed_applications, many=True) |         serializer = self.get_serializer(allowed_applications, many=True) | ||||||
|         return self.get_paginated_response(serializer.data) |         return self.get_paginated_response(serializer.data) | ||||||
|  |  | ||||||
|     @action(detail=True) |     @permission_required("authentik_core.change_application") | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         request_body=no_body, | ||||||
|  |         manual_parameters=[ | ||||||
|  |             openapi.Parameter( | ||||||
|  |                 name="file", | ||||||
|  |                 in_=openapi.IN_FORM, | ||||||
|  |                 type=openapi.TYPE_FILE, | ||||||
|  |                 required=True, | ||||||
|  |             ) | ||||||
|  |         ], | ||||||
|  |         responses={200: "Success", 400: "Bad request"}, | ||||||
|  |     ) | ||||||
|  |     @action( | ||||||
|  |         detail=True, | ||||||
|  |         pagination_class=None, | ||||||
|  |         filter_backends=[], | ||||||
|  |         methods=["POST"], | ||||||
|  |         parser_classes=(MultiPartParser,), | ||||||
|  |     ) | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|  |     def set_icon(self, request: Request, slug: str): | ||||||
|  |         """Set application icon""" | ||||||
|  |         app: Application = self.get_object() | ||||||
|  |         icon = request.FILES.get("file", None) | ||||||
|  |         if not icon: | ||||||
|  |             return HttpResponseBadRequest() | ||||||
|  |         app.meta_icon = icon | ||||||
|  |         app.save() | ||||||
|  |         return Response({}) | ||||||
|  |  | ||||||
|  |     @permission_required( | ||||||
|  |         "authentik_core.view_application", ["authentik_events.view_event"] | ||||||
|  |     ) | ||||||
|  |     @swagger_auto_schema(responses={200: CoordinateSerializer(many=True)}) | ||||||
|  |     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|     def metrics(self, request: Request, slug: str): |     def metrics(self, request: Request, slug: str): | ||||||
|         """Metrics for application logins""" |         """Metrics for application logins""" | ||||||
|         app = get_object_or_404( |         app = self.get_object() | ||||||
|             get_objects_for_user(request.user, "authentik_core.view_application"), |  | ||||||
|             slug=slug, |  | ||||||
|         ) |  | ||||||
|         if not request.user.has_perm("authentik_events.view_event"): |  | ||||||
|             raise Http404 |  | ||||||
|         return Response( |         return Response( | ||||||
|             get_events_per_1h( |             get_events_per_1h( | ||||||
|                 action=EventAction.AUTHORIZE_APPLICATION, |                 action=EventAction.AUTHORIZE_APPLICATION, | ||||||
|  | |||||||
| @ -19,3 +19,6 @@ class GroupViewSet(ModelViewSet): | |||||||
|  |  | ||||||
|     queryset = Group.objects.all() |     queryset = Group.objects.all() | ||||||
|     serializer_class = GroupSerializer |     serializer_class = GroupSerializer | ||||||
|  |     search_fields = ["name", "is_superuser"] | ||||||
|  |     filterset_fields = ["name", "is_superuser"] | ||||||
|  |     ordering = ["name"] | ||||||
|  | |||||||
| @ -1,40 +1,72 @@ | |||||||
| """PropertyMapping API Views""" | """PropertyMapping API Views""" | ||||||
|  | from json import dumps | ||||||
|  |  | ||||||
|  | from drf_yasg.utils import swagger_auto_schema | ||||||
|  | from guardian.shortcuts import get_objects_for_user | ||||||
|  | from rest_framework import mixins | ||||||
|  | from rest_framework.decorators import action | ||||||
|  | from rest_framework.exceptions import PermissionDenied | ||||||
|  | from rest_framework.fields import BooleanField, CharField | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | from rest_framework.viewsets import GenericViewSet | ||||||
|  |  | ||||||
| from authentik.core.api.utils import MetaNameSerializer | from authentik.api.decorators import permission_required | ||||||
|  | from authentik.core.api.utils import ( | ||||||
|  |     MetaNameSerializer, | ||||||
|  |     PassiveSerializer, | ||||||
|  |     TypeCreateSerializer, | ||||||
|  | ) | ||||||
|  | from authentik.core.expression import PropertyMappingEvaluator | ||||||
| from authentik.core.models import PropertyMapping | from authentik.core.models import PropertyMapping | ||||||
|  | from authentik.lib.utils.reflection import all_subclasses | ||||||
|  | from authentik.managed.api import ManagedSerializer | ||||||
|  | from authentik.policies.api.exec import PolicyTestSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): | class PropertyMappingTestResultSerializer(PassiveSerializer): | ||||||
|  |     """Result of a Property-mapping test""" | ||||||
|  |  | ||||||
|  |     result = CharField(read_only=True) | ||||||
|  |     successful = BooleanField(read_only=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyMappingSerializer(ManagedSerializer, ModelSerializer, MetaNameSerializer): | ||||||
|     """PropertyMapping Serializer""" |     """PropertyMapping Serializer""" | ||||||
|  |  | ||||||
|     object_type = SerializerMethodField(method_name="get_type") |     component = SerializerMethodField() | ||||||
|  |  | ||||||
|     def get_type(self, obj): |     def get_component(self, obj: PropertyMapping) -> str: | ||||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" |         """Get object's component so that we know how to edit the object""" | ||||||
|         return obj._meta.object_name.lower().replace("propertymapping", "") |         return obj.component | ||||||
|  |  | ||||||
|     def to_representation(self, instance: PropertyMapping): |     def validate_expression(self, expression: str) -> str: | ||||||
|         # pyright: reportGeneralTypeIssues=false |         """Test Syntax""" | ||||||
|         if instance.__class__ == PropertyMapping: |         evaluator = PropertyMappingEvaluator() | ||||||
|             return super().to_representation(instance) |         evaluator.validate(expression) | ||||||
|         return instance.serializer(instance=instance).data |         return expression | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = PropertyMapping |         model = PropertyMapping | ||||||
|         fields = [ |         fields = [ | ||||||
|             "pk", |             "pk", | ||||||
|  |             "managed", | ||||||
|             "name", |             "name", | ||||||
|             "expression", |             "expression", | ||||||
|             "object_type", |             "component", | ||||||
|             "verbose_name", |             "verbose_name", | ||||||
|             "verbose_name_plural", |             "verbose_name_plural", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMappingViewSet(ReadOnlyModelViewSet): | class PropertyMappingViewSet( | ||||||
|  |     mixins.RetrieveModelMixin, | ||||||
|  |     mixins.DestroyModelMixin, | ||||||
|  |     mixins.ListModelMixin, | ||||||
|  |     GenericViewSet, | ||||||
|  | ): | ||||||
|     """PropertyMapping Viewset""" |     """PropertyMapping Viewset""" | ||||||
|  |  | ||||||
|     queryset = PropertyMapping.objects.none() |     queryset = PropertyMapping.objects.none() | ||||||
| @ -47,3 +79,54 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet): | |||||||
|  |  | ||||||
|     def get_queryset(self): |     def get_queryset(self): | ||||||
|         return PropertyMapping.objects.select_subclasses() |         return PropertyMapping.objects.select_subclasses() | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||||
|  |     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||||
|  |     def types(self, request: Request) -> Response: | ||||||
|  |         """Get all creatable property-mapping types""" | ||||||
|  |         data = [] | ||||||
|  |         for subclass in all_subclasses(self.queryset.model): | ||||||
|  |             subclass: PropertyMapping | ||||||
|  |             data.append( | ||||||
|  |                 { | ||||||
|  |                     "name": subclass._meta.verbose_name, | ||||||
|  |                     "description": subclass.__doc__, | ||||||
|  |                     "component": subclass.component, | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         return Response(TypeCreateSerializer(data, many=True).data) | ||||||
|  |  | ||||||
|  |     @permission_required("authentik_core.view_propertymapping") | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         request_body=PolicyTestSerializer(), | ||||||
|  |         responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"}, | ||||||
|  |     ) | ||||||
|  |     @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) | ||||||
|  |     # pylint: disable=unused-argument, invalid-name | ||||||
|  |     def test(self, request: Request, pk: str) -> Response: | ||||||
|  |         """Test Property Mapping""" | ||||||
|  |         mapping: PropertyMapping = self.get_object() | ||||||
|  |         test_params = PolicyTestSerializer(data=request.data) | ||||||
|  |         if not test_params.is_valid(): | ||||||
|  |             return Response(test_params.errors, status=400) | ||||||
|  |  | ||||||
|  |         # 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 | ||||||
|  |         ) | ||||||
|  |         if not users.exists(): | ||||||
|  |             raise PermissionDenied() | ||||||
|  |  | ||||||
|  |         response_data = {"successful": True, "result": ""} | ||||||
|  |         try: | ||||||
|  |             result = mapping.evaluate( | ||||||
|  |                 users.first(), | ||||||
|  |                 self.request, | ||||||
|  |                 **test_params.validated_data.get("context", {}), | ||||||
|  |             ) | ||||||
|  |             response_data["result"] = dumps(result) | ||||||
|  |         except Exception as exc:  # pylint: disable=broad-except | ||||||
|  |             response_data["result"] = str(exc) | ||||||
|  |             response_data["successful"] = False | ||||||
|  |         response = PropertyMappingTestResultSerializer(response_data) | ||||||
|  |         return Response(response.data) | ||||||
|  | |||||||
| @ -1,10 +1,17 @@ | |||||||
| """Provider API Views""" | """Provider API Views""" | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
|  | from drf_yasg.utils import swagger_auto_schema | ||||||
|  | from rest_framework import mixins | ||||||
|  | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import ReadOnlyField | from rest_framework.fields import ReadOnlyField | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||||
| from rest_framework.viewsets import ModelViewSet | from rest_framework.viewsets import GenericViewSet | ||||||
|  |  | ||||||
| from authentik.core.api.utils import MetaNameSerializer | from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||||
| from authentik.core.models import Provider | from authentik.core.models import Provider | ||||||
|  | from authentik.lib.utils.reflection import all_subclasses | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProviderSerializer(ModelSerializer, MetaNameSerializer): | class ProviderSerializer(ModelSerializer, MetaNameSerializer): | ||||||
| @ -13,11 +20,14 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer): | |||||||
|     assigned_application_slug = ReadOnlyField(source="application.slug") |     assigned_application_slug = ReadOnlyField(source="application.slug") | ||||||
|     assigned_application_name = ReadOnlyField(source="application.name") |     assigned_application_name = ReadOnlyField(source="application.name") | ||||||
|  |  | ||||||
|     object_type = SerializerMethodField() |     component = SerializerMethodField() | ||||||
|  |  | ||||||
|     def get_object_type(self, obj): |     def get_component(self, obj: Provider):  # pragma: no cover | ||||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" |         """Get object component so that we know how to edit the object""" | ||||||
|         return obj._meta.object_name.lower().replace("provider", "") |         # pyright: reportGeneralTypeIssues=false | ||||||
|  |         if obj.__class__ == Provider: | ||||||
|  |             return "" | ||||||
|  |         return obj.component | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
| @ -25,10 +35,9 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer): | |||||||
|         fields = [ |         fields = [ | ||||||
|             "pk", |             "pk", | ||||||
|             "name", |             "name", | ||||||
|             "application", |  | ||||||
|             "authorization_flow", |             "authorization_flow", | ||||||
|             "property_mappings", |             "property_mappings", | ||||||
|             "object_type", |             "component", | ||||||
|             "assigned_application_slug", |             "assigned_application_slug", | ||||||
|             "assigned_application_name", |             "assigned_application_name", | ||||||
|             "verbose_name", |             "verbose_name", | ||||||
| @ -36,7 +45,12 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer): | |||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProviderViewSet(ModelViewSet): | class ProviderViewSet( | ||||||
|  |     mixins.RetrieveModelMixin, | ||||||
|  |     mixins.DestroyModelMixin, | ||||||
|  |     mixins.ListModelMixin, | ||||||
|  |     GenericViewSet, | ||||||
|  | ): | ||||||
|     """Provider Viewset""" |     """Provider Viewset""" | ||||||
|  |  | ||||||
|     queryset = Provider.objects.none() |     queryset = Provider.objects.none() | ||||||
| @ -51,3 +65,26 @@ class ProviderViewSet(ModelViewSet): | |||||||
|  |  | ||||||
|     def get_queryset(self): |     def get_queryset(self): | ||||||
|         return Provider.objects.select_subclasses() |         return Provider.objects.select_subclasses() | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||||
|  |     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||||
|  |     def types(self, request: Request) -> Response: | ||||||
|  |         """Get all creatable provider types""" | ||||||
|  |         data = [] | ||||||
|  |         for subclass in all_subclasses(self.queryset.model): | ||||||
|  |             subclass: Provider | ||||||
|  |             data.append( | ||||||
|  |                 { | ||||||
|  |                     "name": subclass._meta.verbose_name, | ||||||
|  |                     "description": subclass.__doc__, | ||||||
|  |                     "component": subclass().component, | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         data.append( | ||||||
|  |             { | ||||||
|  |                 "name": _("SAML Provider from Metadata"), | ||||||
|  |                 "description": _("Create a SAML Provider by importing its Metadata."), | ||||||
|  |                 "component": "ak-provider-saml-import-form", | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         return Response(TypeCreateSerializer(data, many=True).data) | ||||||
|  | |||||||
| @ -1,37 +1,59 @@ | |||||||
| """Source API Views""" | """Source API Views""" | ||||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | from typing import Iterable | ||||||
| from rest_framework.viewsets import ReadOnlyModelViewSet |  | ||||||
|  |  | ||||||
| from authentik.core.api.utils import MetaNameSerializer | from drf_yasg.utils import swagger_auto_schema | ||||||
|  | from rest_framework import mixins | ||||||
|  | from rest_framework.decorators import action | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from rest_framework.response import Response | ||||||
|  | from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||||
|  | from rest_framework.viewsets import GenericViewSet | ||||||
|  | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||||
| from authentik.core.models import Source | from authentik.core.models import Source | ||||||
|  | from authentik.core.types import UserSettingSerializer | ||||||
|  | from authentik.lib.utils.reflection import all_subclasses | ||||||
|  | from authentik.policies.engine import PolicyEngine | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceSerializer(ModelSerializer, MetaNameSerializer): | class SourceSerializer(ModelSerializer, MetaNameSerializer): | ||||||
|     """Source Serializer""" |     """Source Serializer""" | ||||||
|  |  | ||||||
|     object_type = SerializerMethodField() |     component = SerializerMethodField() | ||||||
|  |  | ||||||
|     def get_object_type(self, obj): |     def get_component(self, obj: Source): | ||||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" |         """Get object component so that we know how to edit the object""" | ||||||
|         return obj._meta.object_name.lower().replace("source", "") |         # pyright: reportGeneralTypeIssues=false | ||||||
|  |         if obj.__class__ == Source: | ||||||
|  |             return "" | ||||||
|  |         return obj.component | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = Source |         model = Source | ||||||
|         fields = SOURCE_SERIALIZER_FIELDS = [ |         fields = [ | ||||||
|             "pk", |             "pk", | ||||||
|             "name", |             "name", | ||||||
|             "slug", |             "slug", | ||||||
|             "enabled", |             "enabled", | ||||||
|             "authentication_flow", |             "authentication_flow", | ||||||
|             "enrollment_flow", |             "enrollment_flow", | ||||||
|             "object_type", |             "component", | ||||||
|             "verbose_name", |             "verbose_name", | ||||||
|             "verbose_name_plural", |             "verbose_name_plural", | ||||||
|  |             "policy_engine_mode", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
| class SourceViewSet(ReadOnlyModelViewSet): | class SourceViewSet( | ||||||
|  |     mixins.RetrieveModelMixin, | ||||||
|  |     mixins.DestroyModelMixin, | ||||||
|  |     mixins.ListModelMixin, | ||||||
|  |     GenericViewSet, | ||||||
|  | ): | ||||||
|     """Source Viewset""" |     """Source Viewset""" | ||||||
|  |  | ||||||
|     queryset = Source.objects.none() |     queryset = Source.objects.none() | ||||||
| @ -40,3 +62,48 @@ class SourceViewSet(ReadOnlyModelViewSet): | |||||||
|  |  | ||||||
|     def get_queryset(self): |     def get_queryset(self): | ||||||
|         return Source.objects.select_subclasses() |         return Source.objects.select_subclasses() | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||||
|  |     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||||
|  |     def types(self, request: Request) -> Response: | ||||||
|  |         """Get all creatable source types""" | ||||||
|  |         data = [] | ||||||
|  |         for subclass in all_subclasses(self.queryset.model): | ||||||
|  |             subclass: Source | ||||||
|  |             component = "" | ||||||
|  |             if subclass._meta.abstract: | ||||||
|  |                 component = subclass.__bases__[0]().component | ||||||
|  |             else: | ||||||
|  |                 component = subclass().component | ||||||
|  |             # pyright: reportGeneralTypeIssues=false | ||||||
|  |             data.append( | ||||||
|  |                 { | ||||||
|  |                     "name": subclass._meta.verbose_name, | ||||||
|  |                     "description": subclass.__doc__, | ||||||
|  |                     "component": component, | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         return Response(TypeCreateSerializer(data, many=True).data) | ||||||
|  |  | ||||||
|  |     @swagger_auto_schema(responses={200: UserSettingSerializer(many=True)}) | ||||||
|  |     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||||
|  |     def user_settings(self, request: Request) -> Response: | ||||||
|  |         """Get all sources the user can configure""" | ||||||
|  |         _all_sources: Iterable[Source] = Source.objects.filter( | ||||||
|  |             enabled=True | ||||||
|  |         ).select_subclasses() | ||||||
|  |         matching_sources: list[UserSettingSerializer] = [] | ||||||
|  |         for source in _all_sources: | ||||||
|  |             user_settings = source.ui_user_settings | ||||||
|  |             if not user_settings: | ||||||
|  |                 continue | ||||||
|  |             policy_engine = PolicyEngine(source, request.user, request) | ||||||
|  |             policy_engine.build() | ||||||
|  |             if not policy_engine.passing: | ||||||
|  |                 continue | ||||||
|  |             source_settings = source.ui_user_settings | ||||||
|  |             source_settings.initial_data["object_uid"] = source.slug | ||||||
|  |             if not source_settings.is_valid(): | ||||||
|  |                 LOGGER.warning(source_settings.errors) | ||||||
|  |             matching_sources.append(source_settings.validated_data) | ||||||
|  |         return Response(matching_sources) | ||||||
|  | |||||||
| @ -1,38 +1,47 @@ | |||||||
| """Tokens API Viewset""" | """Tokens API Viewset""" | ||||||
| from django.db.models.base import Model |  | ||||||
| from django.http.response import Http404 | from django.http.response import Http404 | ||||||
| from drf_yasg2.utils import swagger_auto_schema | from drf_yasg.utils import swagger_auto_schema | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import CharField | from rest_framework.fields import CharField | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ModelSerializer, Serializer | from rest_framework.serializers import ModelSerializer | ||||||
| from rest_framework.viewsets import ModelViewSet | 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 | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
|  | from authentik.managed.api import ManagedSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenSerializer(ModelSerializer): | class TokenSerializer(ManagedSerializer, ModelSerializer): | ||||||
|     """Token Serializer""" |     """Token Serializer""" | ||||||
|  |  | ||||||
|  |     user = UserSerializer(required=False) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = Token |         model = Token | ||||||
|         fields = ["pk", "identifier", "intent", "user", "description"] |         fields = [ | ||||||
|  |             "pk", | ||||||
|  |             "managed", | ||||||
|  |             "identifier", | ||||||
|  |             "intent", | ||||||
|  |             "user", | ||||||
|  |             "description", | ||||||
|  |             "expires", | ||||||
|  |             "expiring", | ||||||
|  |         ] | ||||||
|  |         depth = 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenViewSerializer(Serializer): | class TokenViewSerializer(PassiveSerializer): | ||||||
|     """Show token's current key""" |     """Show token's current key""" | ||||||
|  |  | ||||||
|     key = CharField(read_only=True) |     key = CharField(read_only=True) | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenViewSet(ModelViewSet): | class TokenViewSet(ModelViewSet): | ||||||
|     """Token Viewset""" |     """Token Viewset""" | ||||||
| @ -40,15 +49,37 @@ class TokenViewSet(ModelViewSet): | |||||||
|     lookup_field = "identifier" |     lookup_field = "identifier" | ||||||
|     queryset = Token.filter_not_expired() |     queryset = Token.filter_not_expired() | ||||||
|     serializer_class = TokenSerializer |     serializer_class = TokenSerializer | ||||||
|  |     search_fields = [ | ||||||
|  |         "identifier", | ||||||
|  |         "intent", | ||||||
|  |         "user__username", | ||||||
|  |         "description", | ||||||
|  |     ] | ||||||
|  |     filterset_fields = [ | ||||||
|  |         "identifier", | ||||||
|  |         "intent", | ||||||
|  |         "user__username", | ||||||
|  |         "description", | ||||||
|  |     ] | ||||||
|  |     ordering = ["expires"] | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: TokenViewSerializer(many=False)}) |     def perform_create(self, serializer: TokenSerializer): | ||||||
|     @action(detail=True) |         serializer.save(user=self.request.user) | ||||||
|  |  | ||||||
|  |     @permission_required("authentik_core.view_token_key") | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         responses={ | ||||||
|  |             200: TokenViewSerializer(many=False), | ||||||
|  |             404: "Token not found or expired", | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|     def view_key(self, request: Request, identifier: str) -> Response: |     def view_key(self, request: Request, identifier: str) -> Response: | ||||||
|         """Return token key and log access""" |         """Return token key and log access""" | ||||||
|         tokens = Token.filter_not_expired(identifier=identifier) |         token: Token = self.get_object() | ||||||
|         if not tokens.exists(): |         if token.is_expired: | ||||||
|             raise Http404 |             raise Http404 | ||||||
|         token = tokens.first() |  | ||||||
|         Event.new(EventAction.SECRET_VIEW, secret=token).from_http(  # noqa # nosec |         Event.new(EventAction.SECRET_VIEW, secret=token).from_http(  # noqa # nosec | ||||||
|             request |             request | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -1,34 +1,84 @@ | |||||||
| """User API Views""" | """User API Views""" | ||||||
| from drf_yasg2.utils import swagger_auto_schema | from django.urls import reverse_lazy | ||||||
|  | from django.utils.http import urlencode | ||||||
|  | from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method | ||||||
| from guardian.utils import get_anonymous_user | from guardian.utils import get_anonymous_user | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
|  | from rest_framework.fields import CharField, SerializerMethodField | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ( | from rest_framework.serializers import BooleanField, ModelSerializer | ||||||
|     BooleanField, |  | ||||||
|     ModelSerializer, |  | ||||||
|     SerializerMethodField, |  | ||||||
| ) |  | ||||||
| from rest_framework.viewsets import ModelViewSet | from rest_framework.viewsets import ModelViewSet | ||||||
|  |  | ||||||
| from authentik.core.models import User | from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h | ||||||
| from authentik.lib.templatetags.authentik_utils import avatar | from authentik.api.decorators import permission_required | ||||||
|  | from authentik.core.api.utils import LinkSerializer, PassiveSerializer | ||||||
|  | from authentik.core.middleware import ( | ||||||
|  |     SESSION_IMPERSONATE_ORIGINAL_USER, | ||||||
|  |     SESSION_IMPERSONATE_USER, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import Token, TokenIntents, User | ||||||
|  | from authentik.events.models import EventAction | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserSerializer(ModelSerializer): | class UserSerializer(ModelSerializer): | ||||||
|     """User Serializer""" |     """User Serializer""" | ||||||
|  |  | ||||||
|     is_superuser = BooleanField(read_only=True) |     is_superuser = BooleanField(read_only=True) | ||||||
|     avatar = SerializerMethodField() |     avatar = CharField(read_only=True) | ||||||
|  |  | ||||||
|     def get_avatar(self, user: User) -> str: |  | ||||||
|         """Add user's avatar as URL""" |  | ||||||
|         return avatar(user) |  | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = User |         model = User | ||||||
|         fields = ["pk", "username", "name", "is_superuser", "email", "avatar"] |         fields = [ | ||||||
|  |             "pk", | ||||||
|  |             "username", | ||||||
|  |             "name", | ||||||
|  |             "is_active", | ||||||
|  |             "last_login", | ||||||
|  |             "is_superuser", | ||||||
|  |             "email", | ||||||
|  |             "avatar", | ||||||
|  |             "attributes", | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SessionUserSerializer(PassiveSerializer): | ||||||
|  |     """Response for the /user/me endpoint, returns the currently active user (as `user` property) | ||||||
|  |     and, if this user is being impersonated, the original user in the `original` property.""" | ||||||
|  |  | ||||||
|  |     user = UserSerializer() | ||||||
|  |     original = UserSerializer(required=False) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserMetricsSerializer(PassiveSerializer): | ||||||
|  |     """User Metrics""" | ||||||
|  |  | ||||||
|  |     logins_per_1h = SerializerMethodField() | ||||||
|  |     logins_failed_per_1h = SerializerMethodField() | ||||||
|  |     authorizations_per_1h = SerializerMethodField() | ||||||
|  |  | ||||||
|  |     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||||
|  |     def get_logins_per_1h(self, _): | ||||||
|  |         """Get successful logins per hour for the last 24 hours""" | ||||||
|  |         user = self.context["user"] | ||||||
|  |         return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk) | ||||||
|  |  | ||||||
|  |     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||||
|  |     def get_logins_failed_per_1h(self, _): | ||||||
|  |         """Get failed logins per hour for the last 24 hours""" | ||||||
|  |         user = self.context["user"] | ||||||
|  |         return get_events_per_1h( | ||||||
|  |             action=EventAction.LOGIN_FAILED, context__username=user.username | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||||
|  |     def get_authorizations_per_1h(self, _): | ||||||
|  |         """Get failed logins per hour for the last 24 hours""" | ||||||
|  |         user = self.context["user"] | ||||||
|  |         return get_events_per_1h( | ||||||
|  |             action=EventAction.AUTHORIZE_APPLICATION, user__pk=user.pk | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserViewSet(ModelViewSet): | class UserViewSet(ModelViewSet): | ||||||
| @ -36,13 +86,54 @@ class UserViewSet(ModelViewSet): | |||||||
|  |  | ||||||
|     queryset = User.objects.none() |     queryset = User.objects.none() | ||||||
|     serializer_class = UserSerializer |     serializer_class = UserSerializer | ||||||
|  |     search_fields = ["username", "name", "is_active"] | ||||||
|  |     filterset_fields = ["username", "name", "is_active"] | ||||||
|  |  | ||||||
|     def get_queryset(self): |     def get_queryset(self): | ||||||
|         return User.objects.all().exclude(pk=get_anonymous_user().pk) |         return User.objects.all().exclude(pk=get_anonymous_user().pk) | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: UserSerializer(many=False)}) |     @swagger_auto_schema(responses={200: SessionUserSerializer(many=False)}) | ||||||
|     @action(detail=False) |     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||||
|     # pylint: disable=invalid-name |     # pylint: disable=invalid-name | ||||||
|     def me(self, request: Request) -> Response: |     def me(self, request: Request) -> Response: | ||||||
|         """Get information about current user""" |         """Get information about current user""" | ||||||
|         return Response(UserSerializer(request.user).data) |         serializer = SessionUserSerializer( | ||||||
|  |             data={"user": UserSerializer(request.user).data} | ||||||
|  |         ) | ||||||
|  |         if SESSION_IMPERSONATE_USER in request._request.session: | ||||||
|  |             serializer.initial_data["original"] = UserSerializer( | ||||||
|  |                 request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER] | ||||||
|  |             ).data | ||||||
|  |         serializer.is_valid() | ||||||
|  |         return Response(serializer.data) | ||||||
|  |  | ||||||
|  |     @permission_required("authentik_core.view_user", ["authentik_events.view_event"]) | ||||||
|  |     @swagger_auto_schema(responses={200: UserMetricsSerializer(many=False)}) | ||||||
|  |     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||||
|  |     # pylint: disable=invalid-name, unused-argument | ||||||
|  |     def metrics(self, request: Request, pk: int) -> Response: | ||||||
|  |         """User metrics per 1h""" | ||||||
|  |         user: User = self.get_object() | ||||||
|  |         serializer = UserMetricsSerializer(True) | ||||||
|  |         serializer.context["user"] = user | ||||||
|  |         return Response(serializer.data) | ||||||
|  |  | ||||||
|  |     @permission_required("authentik_core.reset_user_password") | ||||||
|  |     @swagger_auto_schema( | ||||||
|  |         responses={"200": LinkSerializer(many=False)}, | ||||||
|  |     ) | ||||||
|  |     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||||
|  |     # pylint: disable=invalid-name, unused-argument | ||||||
|  |     def recovery(self, request: Request, pk: int) -> Response: | ||||||
|  |         """Create a temporary link that a user can use to recover their accounts""" | ||||||
|  |         user: User = self.get_object() | ||||||
|  |         token, __ = Token.objects.get_or_create( | ||||||
|  |             identifier=f"{user.uid}-password-reset", | ||||||
|  |             user=user, | ||||||
|  |             intent=TokenIntents.INTENT_RECOVERY, | ||||||
|  |         ) | ||||||
|  |         querystring = urlencode({"token": token.key}) | ||||||
|  |         link = request.build_absolute_uri( | ||||||
|  |             reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}" | ||||||
|  |         ) | ||||||
|  |         return Response({"link": link}) | ||||||
|  | |||||||
| @ -1,20 +1,25 @@ | |||||||
| """API Utilities""" | """API Utilities""" | ||||||
| from django.db.models import Model | from django.db.models import Model | ||||||
|  | from rest_framework.fields import CharField, IntegerField | ||||||
| from rest_framework.serializers import Serializer, SerializerMethodField | from rest_framework.serializers import Serializer, SerializerMethodField | ||||||
|  |  | ||||||
|  |  | ||||||
| class MetaNameSerializer(Serializer): | class PassiveSerializer(Serializer): | ||||||
|  |     """Base serializer class which doesn't implement create/update methods""" | ||||||
|  |  | ||||||
|  |     def create(self, validated_data: dict) -> Model: | ||||||
|  |         return Model() | ||||||
|  |  | ||||||
|  |     def update(self, instance: Model, validated_data: dict) -> Model: | ||||||
|  |         return Model() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MetaNameSerializer(PassiveSerializer): | ||||||
|     """Add verbose names to response""" |     """Add verbose names to response""" | ||||||
|  |  | ||||||
|     verbose_name = SerializerMethodField() |     verbose_name = SerializerMethodField() | ||||||
|     verbose_name_plural = SerializerMethodField() |     verbose_name_plural = SerializerMethodField() | ||||||
|  |  | ||||||
|     def create(self, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def update(self, instance: Model, validated_data: dict) -> Model: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     def get_verbose_name(self, obj: Model) -> str: |     def get_verbose_name(self, obj: Model) -> str: | ||||||
|         """Return object's verbose_name""" |         """Return object's verbose_name""" | ||||||
|         return obj._meta.verbose_name |         return obj._meta.verbose_name | ||||||
| @ -22,3 +27,23 @@ class MetaNameSerializer(Serializer): | |||||||
|     def get_verbose_name_plural(self, obj: Model) -> str: |     def get_verbose_name_plural(self, obj: Model) -> str: | ||||||
|         """Return object's plural verbose_name""" |         """Return object's plural verbose_name""" | ||||||
|         return obj._meta.verbose_name_plural |         return obj._meta.verbose_name_plural | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TypeCreateSerializer(PassiveSerializer): | ||||||
|  |     """Types of an object that can be created""" | ||||||
|  |  | ||||||
|  |     name = CharField(required=True) | ||||||
|  |     description = CharField(required=True) | ||||||
|  |     component = CharField(required=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CacheSerializer(PassiveSerializer): | ||||||
|  |     """Generic cache stats for an object""" | ||||||
|  |  | ||||||
|  |     count = IntegerField(read_only=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LinkSerializer(PassiveSerializer): | ||||||
|  |     """Returns a single link""" | ||||||
|  |  | ||||||
|  |     link = CharField() | ||||||
|  | |||||||
| @ -14,3 +14,4 @@ class AuthentikCoreConfig(AppConfig): | |||||||
|  |  | ||||||
|     def ready(self): |     def ready(self): | ||||||
|         import_module("authentik.core.signals") |         import_module("authentik.core.signals") | ||||||
|  |         import_module("authentik.core.managed") | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	