From 10e6afe351ac136e428493978e302cf3453b9568 Mon Sep 17 00:00:00 2001 From: Evilham Date: Sun, 11 Dec 2022 14:00:47 +0100 Subject: [PATCH] [dd-sso] Add tests and refactor API These tests can be executed with: python -m unittest discover -s admin.views.test --- dd-sso/admin/Pipfile | 2 + dd-sso/admin/Pipfile.lock | 745 +++++++++++------- dd-sso/admin/setup.cfg | 5 + dd-sso/admin/src/admin/views/ApiViews.py | 552 +++++++------ dd-sso/admin/src/admin/views/test/__init__.py | 0 dd-sso/admin/src/admin/views/test/mocks.py | 243 ++++++ .../src/admin/views/test/test_ApiViews.py | 555 +++++++++++++ 7 files changed, 1559 insertions(+), 543 deletions(-) create mode 100644 dd-sso/admin/setup.cfg create mode 100644 dd-sso/admin/src/admin/views/test/__init__.py create mode 100644 dd-sso/admin/src/admin/views/test/mocks.py create mode 100644 dd-sso/admin/src/admin/views/test/test_ApiViews.py diff --git a/dd-sso/admin/Pipfile b/dd-sso/admin/Pipfile index 9dba800..9618867 100644 --- a/dd-sso/admin/Pipfile +++ b/dd-sso/admin/Pipfile @@ -32,6 +32,8 @@ types-psycopg2 = "*" types-pyyaml = "*" types-python-jose = "*" types-pillow = "*" +flask-unittest = "*" +flake8 = "*" [requires] python_version = "3.8" diff --git a/dd-sso/admin/Pipfile.lock b/dd-sso/admin/Pipfile.lock index 5508294..6d392e9 100644 --- a/dd-sso/admin/Pipfile.lock +++ b/dd-sso/admin/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8a5f88b027753cb1145b10e191326d6e9cfaa1c3333a773ac91071c3e7b7008c" + "sha256": "cd4a56afb09ac033e44f2b8c075a8103f5ee27b31b766508441e34539f654ea1" }, "pipfile-spec": 6, "requires": { @@ -41,11 +41,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.12.7" }, "cffi": { "hashes": [ @@ -118,11 +118,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -142,31 +142,35 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd", + "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db", + "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290", + "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744", + "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb", + "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d", + "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70", + "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b", + "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876", + "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083", + "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6", + "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1", + "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00", + "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b", + "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b", + "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285", + "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9", + "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0", + "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d", + "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2", + "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8", + "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee", + "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b", + "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7", + "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353", + "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c" ], "index": "pypi", - "version": "==37.0.4" + "version": "==38.0.4" }, "diceware": { "hashes": [ @@ -181,7 +185,7 @@ "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==2.2.1" }, "ecdsa": { @@ -194,19 +198,19 @@ }, "eventlet": { "hashes": [ - "sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31", - "sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515" + "sha256:82c382c2a2c712f1a8320378a9120ac9589d9f1131c36a63780f0b8504afa5bc", + "sha256:96039b9389dbb4431b1c0a6e42ea1326628cc7ad63a6280b02947f111d3d8e04" ], "index": "pypi", - "version": "==0.33.1" + "version": "==0.33.2" }, "flask": { "hashes": [ - "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb", - "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c" + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" ], "index": "pypi", - "version": "==2.1.3" + "version": "==2.2.2" }, "flask-login": { "hashes": [ @@ -218,88 +222,93 @@ }, "flask-socketio": { "hashes": [ - "sha256:19c3d0cea49c53505fa457fedc133b32cb6eeaaa30d28cdab9d6ca8f16045427", - "sha256:c82672b843fa271ec2961d356c608bc94a730660ac73a623bddb66c4b3d72215" + "sha256:04093e3ca144467f9731c376796aff105d182ac4cccfcb056d05d567a675f306", + "sha256:11d1d78b8805cda351b27828a110b88c74a573be62b07f7f5a519fb67fae0a58" ], "index": "pypi", - "version": "==5.2.0" + "version": "==5.3.2" }, "greenlet": { "hashes": [ - "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", - "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", - "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", - "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", - "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", - "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", - "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", - "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", - "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", - "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", - "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", - "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", - "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", - "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", - "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", - "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", - "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", - "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", - "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", - "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", - "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", - "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", - "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", - "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", - "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", - "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", - "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", - "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", - "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", - "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", - "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", - "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", - "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", - "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", - "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", - "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", - "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", - "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", - "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", - "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", - "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", - "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", - "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", - "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", - "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", - "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", - "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", - "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", - "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", - "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", - "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", - "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", - "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", - "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", - "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" + "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9", + "sha256:0459d94f73265744fee4c2d5ec44c6f34aa8a31017e6e9de770f7bcf29710be9", + "sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581", + "sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26", + "sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd", + "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2", + "sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a", + "sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82", + "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a", + "sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce", + "sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f", + "sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524", + "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48", + "sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77", + "sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928", + "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e", + "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67", + "sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9", + "sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68", + "sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd", + "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515", + "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5", + "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39", + "sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94", + "sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92", + "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e", + "sha256:662e8f7cad915ba75d8017b3e601afc01ef20deeeabf281bd00369de196d7726", + "sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd", + "sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5", + "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764", + "sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955", + "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608", + "sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148", + "sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51", + "sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9", + "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d", + "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c", + "sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72", + "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1", + "sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2", + "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23", + "sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb", + "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6", + "sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19", + "sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45", + "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000", + "sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da", + "sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617", + "sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963", + "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7", + "sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d", + "sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d", + "sha256:d38ffd0e81ba8ef347d2be0772e899c289b59ff150ebbbbe05dc61b1246eb4e0", + "sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243", + "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce", + "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6", + "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a", + "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1", + "sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f", + "sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.1.2" + "version": "==2.0.1" }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.3" + "version": "==3.4" }, "importlib-metadata": { "hashes": [ - "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", - "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" + "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b", + "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" ], "markers": "python_version < '3.10'", - "version": "==4.12.0" + "version": "==5.1.0" }, "itsdangerous": { "hashes": [ @@ -365,102 +374,110 @@ }, "minio": { "hashes": [ - "sha256:12ac2d1d4fd3cea159d625847445e1bfceba3fbc2f4ab692c2d2bf716f82246c", - "sha256:1cab424275749b8b5b8bb0c6cc856d667305ef549796ae56f3237fe55306a1fc" + "sha256:63111fedf67e07c5a4c8948b3a4e5ecbb372b522ea562bfa4d484194ec6a2b99", + "sha256:c8ab8646f93d47b9aefbf4db76aaba5ac54c87454b922a3d6c1423aed050aad5" ], "index": "pypi", - "version": "==7.1.11" + "version": "==7.1.12" }, "mysql-connector-python": { "hashes": [ - "sha256:1d9d3af14594aceda2c3096564b4c87ffac21e375806a802daeaf7adcd18d36b", - "sha256:234c6b156a1989bebca6eb564dc8f2e9d352f90a51bd228ccd68eb66fcd5fd7a", - "sha256:33c4e567547a9a1868462fda8f2b19ea186a7b1afe498171dca39c0f3aa43a75", - "sha256:36e763f21e62b3c9623a264f2513ee11924ea1c9cc8640c115a279d3087064be", - "sha256:41a04d1900e366bf6c2a645ead89ab9a567806d5ada7d417a3a31f170321dd14", - "sha256:47deb8c3324db7eb2bfb720ec8084d547b1bce457672ea261bc21836024249db", - "sha256:59a8592e154c874c299763bb8aa12c518384c364bcfd0d193e85c869ea81a895", - "sha256:611c6945805216104575f7143ff6497c87396ce82d3257e6da7257b65406f13e", - "sha256:62266d1b18cb4e286a05df0e1c99163a4955c82d41045305bcf0ab2aac107843", - "sha256:712cdfa97f35fec715e8d7aaa15ed9ce04f3cf71b3c177fcca273047040de9f2", - "sha256:7f771bd5cba3ade6d9f7a649e65d7c030f69f0e69980632b5cbbd3d19c39cee5", - "sha256:8876b1d51cae33cdfe7021d68206661e94dcd2666e5e14a743f8321e2b068e84", - "sha256:8b7d50c221320b0e609dce9ca8801ab2f2a748dfee65cd76b1e4c6940757734a", - "sha256:954a1fc2e9a811662c5b17cea24819c020ff9d56b2ff8e583dd0a233fb2399f6", - "sha256:a130c5489861c7ff2990e5b503c37beb2fb7b32211b92f9107ad864ee90654c0", - "sha256:b5dc0f3295e404f93b674bfaff7589a9fbb8b5ae6c1c134112a1d1beb2f664b2", - "sha256:ce23ca9c27e1f7b4707b3299ce515125f312736d86a7e5b2aa778484fa3ffa10", - "sha256:d8f74c9388176635f75c01d47d0abc783a47e58d7f36d04fb6ee40ab6fb35c9b", - "sha256:f1d40cac9c786e292433716c1ade7a8968cbc3ea177026697b86a63188ddba34", - "sha256:f1eb74eb30bb04ff314f5e19af5421d23b504e41d16ddcee2603b4100d18fd68", - "sha256:f5d812245754d4759ebc8c075662fef65397e1e2a438a3c391eac9d545077b8b" + "sha256:02526f16eacc3961ff681c5c8455d2306a9b45124f2f012ca75a1eac9ceb5165", + "sha256:0fbe8f5441ad781b4f65c54a10ac77c6a329591456607e042786528599519636", + "sha256:17d6ea22dacca7fa78a73a81f2b186d4c5c6e70b7be314e352526654e9ba4713", + "sha256:28cb3667be64ebfbd3d477bbd2c71e50d48bd5ed7ba2072dd460ae886d27e88e", + "sha256:30f4542d4d20357c79604e6bf1a801e71dfc45c759c22b502ca5aa8122c3e859", + "sha256:3e271d8de00d5e9f9bd4b212c8e23d2986dead0f20379010f3b274a3e24cbfcb", + "sha256:447847396d1b51edd9cfe05a8c5ba82836d8ea4866f25f36a836cab322fdc4f0", + "sha256:48eb34f4e69a2fba56f310de6682862a15d46cd2bd51ee6eebc3a244e4ee0aa6", + "sha256:4d75e6c3a7f18004e8279cbd9f5edc70089d6aaf3cb64374e21098d9bf0b93c4", + "sha256:5e01a2f50378c13407a32e40dd4d225cfee5996d9d11968f76720ec28aa45421", + "sha256:744c976569e81eecce5e8c7e8f80df2a1c3f64414829addc69c64aef8f56d091", + "sha256:746df133c677fbe4687da33aad5a711abdd9bd2277bbc350e20f903f07c81ef5", + "sha256:79d6a6e8ce955df5ca0786cb8ed8fbd999745c9b50def89993a2a0f4732de721", + "sha256:8ad0d08f3f7c9e48d6d102c7de718e5e44f630f916ff2f4b4ff8a3756b5d10ac", + "sha256:9be9c4dcae987a2a3f07b2ad984984c24f90887dbfab3c8a971e631ad4ca5ccf", + "sha256:a1d8c1509c740649f352400d50360185e5473371507bb6498ceda0c6e877920c", + "sha256:a570a72e0015b36b9c0775ae27c1d4946225f02f62129d16a14e9d77a38c0717", + "sha256:a7ac859a52486ac319e37f61469bbb9023faef38018223efa74e953f1fe23d36", + "sha256:ac85883ec3b3a9a0e36cacc89b8f5e666206842c432a5f69b09a7687ddf51d4a", + "sha256:ae1b3d03802474a161cce8a97024484d18bef43b86d20114908cbc263817cade", + "sha256:b2bbf443f6346e46c26a3e91dd96a428a1038f2d3c5e466541078479c64a1833", + "sha256:d0ca1ba3e5fb2f2cddcf271c320cd5c368f8d392c034ddab7a1c8dfd19510351", + "sha256:e60426af313dcd526028d018d70757a82c5cc0673776b2a614e2180b5970feed", + "sha256:e9e5ad544adfc82ffbda2c74685c8c953bce2e212c56f117020079f05e2c68b2", + "sha256:f3ee04a601f9cb90ace9618bbe2fa8e5bb59be3eb0c2bd8a5405fe69e05e446b", + "sha256:f89b7a731885b8a04248e4d8d124705ca836f0ddd3b7cf0c789e21f4b32810ed" ], "index": "pypi", - "version": "==8.0.30" + "version": "==8.0.31" }, "pillow": { "hashes": [ - "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", - "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", - "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", - "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", - "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", - "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", - "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", - "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", - "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", - "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", - "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", - "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", - "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", - "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", - "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", - "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", - "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", - "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", - "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", - "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", - "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", - "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", - "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", - "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", - "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", - "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", - "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", - "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", - "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", - "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", - "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", - "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", - "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", - "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", - "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", - "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", - "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", - "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", - "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", - "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", - "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", - "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", - "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", - "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", - "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", - "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", - "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", - "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", - "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", - "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", - "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", - "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", - "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", - "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", - "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", - "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", - "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", - "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" + "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040", + "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8", + "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65", + "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2", + "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627", + "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07", + "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef", + "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535", + "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c", + "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc", + "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3", + "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1", + "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c", + "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa", + "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32", + "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502", + "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4", + "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f", + "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812", + "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636", + "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20", + "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c", + "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91", + "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe", + "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b", + "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad", + "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9", + "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72", + "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4", + "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de", + "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29", + "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee", + "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c", + "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7", + "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11", + "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c", + "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c", + "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448", + "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b", + "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20", + "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228", + "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd", + "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699", + "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b", + "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2", + "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4", + "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c", + "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f", + "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2", + "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c", + "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3", + "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193", + "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48", + "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02", + "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8", + "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e", + "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f", + "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b", + "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74", + "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb", + "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0" ], "index": "pypi", - "version": "==9.2.0" + "version": "==9.3.0" }, "protobuf": { "hashes": [ @@ -494,20 +511,22 @@ }, "psycopg2": { "hashes": [ - "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c", - "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf", - "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362", - "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7", - "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461", - "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126", - "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981", - "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56", - "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305", - "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2", - "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca" + "sha256:093e3894d2d3c592ab0945d9eba9d139c139664dcf83a1c440b8a7aa9bb21955", + "sha256:190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa", + "sha256:1a5c7d7d577e0eabfcf15eb87d1e19314c8c4f0e722a301f98e0e3a65e238b4e", + "sha256:1e5a38aa85bd660c53947bd28aeaafb6a97d70423606f1ccb044a03a1203fe4a", + "sha256:322fd5fca0b1113677089d4ebd5222c964b1760e361f151cbb2706c4912112c5", + "sha256:4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee", + "sha256:920bf418000dd17669d2904472efeab2b20546efd0548139618f8fa305d1d7ad", + "sha256:922cc5f0b98a5f2b1ff481f5551b95cd04580fd6f0c72d9b22e6c0145a4840e0", + "sha256:a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a", + "sha256:b9ac1b0d8ecc49e05e4e182694f418d27f3aedcfca854ebd6c05bb1cffa10d6d", + "sha256:d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f", + "sha256:f5b6320dbc3cf6cfb9f25308286f9f7ab464e65cfb105b64cc9c52831748ced2", + "sha256:fc04dd5189b90d825509caa510f20d1d504761e78b8dfb95a0ede180f71d50e5" ], "index": "pypi", - "version": "==2.9.3" + "version": "==2.9.5" }, "pyasn1": { "hashes": [ @@ -536,11 +555,11 @@ }, "python-engineio": { "hashes": [ - "sha256:18474c452894c60590b2d2339d6c81b93fb9857f1be271a2e91fb2707eb4095d", - "sha256:e660fcbac7497f105310d933987d3a82d2e677240a6b493c0a514aa7f91d3b07" + "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c", + "sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae" ], "markers": "python_version >= '3.6'", - "version": "==4.3.3" + "version": "==4.3.4" }, "python-jose": { "hashes": [ @@ -552,22 +571,23 @@ }, "python-keycloak": { "hashes": [ - "sha256:140d95ca08a2acae1f4cdaf6130bbba3ba3309f059326f7bebd87f150561c4b4", - "sha256:3b504ef117f2192197732a01041f4c2c14d324baf2069f5a97533e11191cf3e6" + "sha256:08c530ff86f631faccb8033d9d9345cc3148cb2cf132ff7564f025292e4dbd96", + "sha256:a1ce102b978beb56d385319b3ca20992b915c2c12d15a2d0c23f1104882f3fb6" ], "index": "pypi", - "version": "==2.1.1" + "version": "==2.6.0" }, "python-socketio": { "hashes": [ - "sha256:5011a0cd2545c954d7df09eef7489ec424c93b001cc146599cd72f1dd20f0d46", - "sha256:86ee93591c1e781d339d9a61940e62fd6cbc838390653b52a7bcc4f7ce89fe47" + "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097", + "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3" ], "markers": "python_version >= '3.6'", - "version": "==5.7.1" + "version": "==5.7.2" }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -579,26 +599,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -625,7 +651,7 @@ "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.9" }, "schema": { @@ -638,11 +664,11 @@ }, "setuptools": { "hashes": [ - "sha256:0d33c374d41c7863419fc8f6c10bfe25b7b498aa34164d135c622e52580c6b16", - "sha256:c04b44a57a6265fe34a4a444e965884716d34bae963119a76353434d6f18e450" + "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54", + "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75" ], "markers": "python_version >= '3.7'", - "version": "==63.2.0" + "version": "==65.6.3" }, "six": { "hashes": [ @@ -654,58 +680,47 @@ }, "urllib3": { "hashes": [ - "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", - "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", - "version": "==1.26.11" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.13" }, "werkzeug": { "hashes": [ - "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", - "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "zipp": { "hashes": [ - "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", - "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" + "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", + "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" ], "markers": "python_version >= '3.7'", - "version": "==3.8.1" + "version": "==3.11.0" } }, "develop": { "black": { "hashes": [ - "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90", - "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c", - "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78", - "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4", - "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee", - "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e", - "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e", - "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6", - "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9", - "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c", - "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256", - "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f", - "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2", - "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c", - "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b", - "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807", - "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf", - "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def", - "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad", - "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d", - "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849", - "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69", - "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666" + "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320", + "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351", + "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350", + "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f", + "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf", + "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148", + "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4", + "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d", + "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc", + "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d", + "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2", + "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f" ], "index": "pypi", - "version": "==22.6.0" + "version": "==22.12.0" }, "click": { "hashes": [ @@ -715,6 +730,38 @@ "markers": "python_version >= '3.7'", "version": "==8.1.3" }, + "flake8": { + "hashes": [ + "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7", + "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181" + ], + "index": "pypi", + "version": "==6.0.0" + }, + "flask": { + "hashes": [ + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" + ], + "index": "pypi", + "version": "==2.2.2" + }, + "flask-unittest": { + "hashes": [ + "sha256:057470ddcfe188b28dae6ebfbda6006d88ca4c37d49e657de3533aa6eeba51d0", + "sha256:a847e1a56d2694040269c3b7fd6f7d67cb33615ae0549d32e3688d6276bda8bd" + ], + "index": "pypi", + "version": "==0.1.3" + }, + "importlib-metadata": { + "hashes": [ + "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b", + "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" + ], + "markers": "python_version < '3.10'", + "version": "==5.1.0" + }, "isort": { "hashes": [ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", @@ -723,34 +770,111 @@ "index": "pypi", "version": "==5.10.1" }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, "mypy": { "hashes": [ - "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", - "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", - "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", - "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", - "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0", - "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", - "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", - "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09", - "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417", - "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", - "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2", - "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", - "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", - "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8", - "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", - "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", - "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", - "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", - "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", - "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca", - "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", - "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", - "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c" + "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d", + "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6", + "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf", + "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f", + "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813", + "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33", + "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad", + "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05", + "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297", + "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06", + "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd", + "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243", + "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305", + "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476", + "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711", + "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70", + "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5", + "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461", + "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab", + "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c", + "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d", + "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135", + "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93", + "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648", + "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a", + "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb", + "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3", + "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372", + "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb", + "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef" ], "index": "pypi", - "version": "==0.971" + "version": "==0.991" }, "mypy-extensions": { "hashes": [ @@ -761,25 +885,42 @@ }, "pathspec": { "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6", + "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6" ], - "version": "==0.9.0" + "markers": "python_version >= '3.7'", + "version": "==0.10.3" }, "platformdirs": { "hashes": [ - "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", - "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" + "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", + "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" ], "markers": "python_version >= '3.7'", - "version": "==2.5.2" + "version": "==2.6.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", + "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" + ], + "markers": "python_version >= '3.6'", + "version": "==2.10.0" + }, + "pyflakes": { + "hashes": [ + "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf", + "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.1" }, "tomli": { "hashes": [ "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "markers": "python_version < '3.11'", + "markers": "python_full_version < '3.11.0a7'", "version": "==2.0.1" }, "types-click": { @@ -813,19 +954,19 @@ }, "types-pillow": { "hashes": [ - "sha256:9781104ee2176f680576523fa2a2b83b134957aec6f4d62582cc9e74c93a60b4", - "sha256:d63743ef631e47f8d8669590ea976162321a9a7604588b424b6306533453fb63" + "sha256:98b8484ff343676f6f7051682a6cfd26896e993e86b3ce9badfa0ec8750f5405", + "sha256:c18d466dc18550d96b8b4a279ff94f0cbad696825b5ad55466604f1daf5709de" ], "index": "pypi", - "version": "==9.2.1" + "version": "==9.3.0.4" }, "types-psycopg2": { "hashes": [ - "sha256:14c779dcab18c31453fa1cad3cf4b1601d33540a344adead3c47a6b8091cd2fa", - "sha256:9b0e9e1f097b15cd9fa8aad2596a9e3082fd72f8d9cfe52b190cfa709105b6c0" + "sha256:084558d6bc4b2cfa249b06be0fdd9a14a69d307bae5bb5809a2f14cfbaa7a23f", + "sha256:bff045579642ce00b4a3c8f2e401b7f96dfaa34939f10be64b0dd3b53feca57d" ], "index": "pypi", - "version": "==2.9.18" + "version": "==2.9.21.2" }, "types-python-jose": { "hashes": [ @@ -837,26 +978,26 @@ }, "types-pyyaml": { "hashes": [ - "sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0", - "sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989" + "sha256:1e94e80aafee07a7e798addb2a320e32956a373f376655128ae20637adb2655b", + "sha256:6840819871c92deebe6a2067fb800c11b8a063632eb4e3e755914e7ab3604e83" ], "index": "pypi", - "version": "==6.0.11" + "version": "==6.0.12.2" }, "types-requests": { "hashes": [ - "sha256:98ab647ae88b5e2c41d6d20cfcb5117da1bea561110000b6fdeeea07b3e89877", - "sha256:ac618bfefcb3742eaf97c961e13e9e5a226e545eda4a3dbe293b898d40933ad1" + "sha256:091d4a5a33c1b4f20d8b1b952aa8fa27a6e767c44c3cf65e56580df0b05fd8a9", + "sha256:a7df37cc6fb6187a84097da951f8e21d335448aa2501a6b0a39cbd1d7ca9ee2a" ], "index": "pypi", - "version": "==2.28.5" + "version": "==2.28.11.5" }, "types-urllib3": { "hashes": [ - "sha256:0d027fcd27dbb3cb532453b4d977e05bc1e13aefd70519866af211b3003d895d", - "sha256:73fd274524c3fc7cd8cd9ceb0cb67ed99b45f9cb2831013e46d50c1451044800" + "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49", + "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee" ], - "version": "==1.26.17" + "version": "==1.26.25.4" }, "types-werkzeug": { "hashes": [ @@ -867,11 +1008,27 @@ }, "typing-extensions": { "hashes": [ - "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" + "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", + "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + ], + "markers": "python_version < '3.10'", + "version": "==4.4.0" + }, + "werkzeug": { + "hashes": [ + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "markers": "python_version >= '3.7'", - "version": "==4.3.0" + "version": "==2.2.2" + }, + "zipp": { + "hashes": [ + "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa", + "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766" + ], + "markers": "python_version >= '3.7'", + "version": "==3.11.0" } } } diff --git a/dd-sso/admin/setup.cfg b/dd-sso/admin/setup.cfg new file mode 100644 index 0000000..844409b --- /dev/null +++ b/dd-sso/admin/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +profile = "black" +max-line-length = 88 +extend-ignore = E203 +statistics = True diff --git a/dd-sso/admin/src/admin/views/ApiViews.py b/dd-sso/admin/src/admin/views/ApiViews.py index e371a8e..d9ebd1c 100644 --- a/dd-sso/admin/src/admin/views/ApiViews.py +++ b/dd-sso/admin/src/admin/views/ApiViews.py @@ -18,319 +18,373 @@ # along with DD. If not, see . # # SPDX-License-Identifier: AGPL-3.0-or-later +import copy import json import logging as log -from operator import itemgetter -import os -import socket -import sys -import time import traceback +from operator import itemgetter +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional from flask import request -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional if TYPE_CHECKING: from admin.flaskapp import AdminFlaskApp from ..lib.api_exceptions import Error -from .decorators import has_token, OptionalJsonResponse +from .decorators import OptionalJsonResponse, has_token + +ERR_500 = ( + json.dumps({"msg": "Internal server error", "code": 500, "error": True}), + 500, + {"Content-Type": "application/json"}, +) +ERR_501 = ( + json.dumps({"msg": "Not implemented yet", "code": 501, "error": True}), + 501, + {"Content-Type": "application/json"}, +) +ERR_400 = ( + json.dumps({"msg": "Bad request", "code": 400, "error": True}), + 400, + {"Content-Type": "application/json"}, +) +ERR_404 = ( + json.dumps({"msg": "Not found", "code": 404, "error": True}), + 404, + {"Content-Type": "application/json"}, +) +ERR_409 = ( + json.dumps({"msg": "Conflict", "code": 409, "error": True}), + 409, + {"Content-Type": "application/json"}, +) +ERR_412 = ( + json.dumps({"msg": "Precondition failed", "code": 412, "error": True}), + 412, + {"Content-Type": "application/json"}, +) -def setup_api_views(app : "AdminFlaskApp") -> None: - ## LISTS +def setup_api_views(app: "AdminFlaskApp") -> None: + # LISTS @app.json_route("/ddapi/users", methods=["GET"]) @has_token def ddapi_users() -> OptionalJsonResponse: - if request.method == "GET": - sorted_users = sorted(app.admin.get_mix_users(), key=itemgetter("username")) - users = [] - for user in sorted_users: - users.append(user_parser(user)) - return json.dumps(users), 200, {"Content-Type": "application/json"} + try: + if request.method == "GET": + sorted_users = sorted( + app.admin.get_mix_users(), key=itemgetter("username") + ) + users = [] + for user in sorted_users: + users.append(user_parser(user)) + return json.dumps(users), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/users/filter", methods=["POST"]) @has_token def ddapi_users_search() -> OptionalJsonResponse: - if request.method == "POST": - data = request.get_json(force=True) - if not data.get("text"): - raise Error("bad_request", "Incorrect data requested.") - users = app.admin.get_mix_users() - result = [user_parser(user) for user in filter_users(users, data["text"])] - sorted_result = sorted(result, key=itemgetter("id")) - return json.dumps(sorted_result), 200, {"Content-Type": "application/json"} + try: + if request.method == "POST": + try: + data = request.get_json(force=True) + if not data.get("text"): + raise Error("bad_request", "Incorrect data requested.") + except Exception: + return ERR_400 + users = app.admin.get_mix_users() + result = [ + user_parser(user) for user in filter_users(users, data["text"]) + ] + sorted_result = sorted(result, key=itemgetter("id")) + return ( + json.dumps(sorted_result), + 200, + {"Content-Type": "application/json"}, + ) + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/groups", methods=["GET"]) @has_token def ddapi_groups() -> OptionalJsonResponse: - if request.method == "GET": - sorted_groups = sorted(app.admin.get_mix_groups(), key=itemgetter("name")) - groups = [] - for group in sorted_groups: - groups.append(group_parser(group)) - return json.dumps(groups), 200, {"Content-Type": "application/json"} - return None - - @app.json_route("/ddapi/group/users", methods=["POST"]) - @has_token - def ddapi_group_users() -> OptionalJsonResponse: - if request.method == "POST": - data = request.get_json(force=True) - sorted_users = sorted(app.admin.get_mix_users(), key=itemgetter("username")) - if data.get("id"): - group_users = [ - user_parser(user) - for user in sorted_users - if data.get("id") in user["keycloak_groups"] - ] - elif data.get("path"): - try: - name = [ - g["name"] - for g in app.admin.get_mix_groups() - if g["path"] == data.get("path") - ][0] - group_users = [ - user_parser(user) - for user in sorted_users - if name in user["keycloak_groups"] - ] - except: - raise Error("not_found", "Group path not found in system") - elif data.get("keycloak_id"): - try: - name = [ - g["name"] - for g in app.admin.get_mix_groups() - if g["id"] == data.get("keycloak_id") - ][0] - group_users = [ - user_parser(user) - for user in sorted_users - if name in user["keycloak_groups"] - ] - except: - raise Error("not_found", "Group keycloak_id not found in system") - else: - raise Error("bad_request", "Incorrect data requested.") - return json.dumps(group_users), 200, {"Content-Type": "application/json"} + try: + if request.method == "GET": + sorted_groups = sorted( + app.admin.get_mix_groups(), key=itemgetter("name") + ) + groups = [] + for group in sorted_groups: + groups.append(group_parser(group)) + return json.dumps(groups), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/roles", methods=["GET"]) @has_token def ddapi_roles() -> OptionalJsonResponse: - if request.method == "GET": - roles = [] - for role in sorted(app.admin.get_roles(), key=itemgetter("name")): - log.error(role) - roles.append( - { - "keycloak_id": role["id"], - "id": role["name"], - "name": role["name"], - "description": role.get("description", ""), - } - ) - return json.dumps(roles), 200, {"Content-Type": "application/json"} + try: + if request.method == "GET": + roles = [] + for role in sorted(app.admin.get_roles(), key=itemgetter("name")): + roles.append(role_parser(role)) + return json.dumps(roles), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/role/users", methods=["POST"]) @has_token def ddapi_role_users() -> OptionalJsonResponse: - if request.method == "POST": - data = request.get_json(force=True) - sorted_users = sorted(app.admin.get_mix_users(), key=itemgetter("username")) - if data.get("id", data.get("name")): - role_users = [ - user_parser(user) - for user in sorted_users - if data.get("id", data.get("name")) in user["roles"] - ] - elif data.get("keycloak_id"): + try: + if request.method == "POST": try: - id = [ - r["id"] - for r in app.admin.get_roles() - if r["id"] == data.get("keycloak_id") - ][0] + data = request.get_json(force=True) + assert isinstance(data, dict) + except Exception: + return ERR_400 + sorted_users = sorted( + app.admin.get_mix_users(), key=itemgetter("username") + ) + role: str = data.get("id", "") + if not role: + role = data.get("name", "") + if not role: + role = data.get("keycloak_id", "") + if role: role_users = [ - user_parser(user) for user in sorted_users if id in user["roles"] + user_parser(user) + for user in sorted_users + if role in user["roles"] ] - except: - raise Error("not_found", "Role keycloak_id not found in system") - else: - raise Error("bad_request", "Incorrect data requested.") - return json.dumps(role_users), 200, {"Content-Type": "application/json"} + else: + return ERR_400 + return json.dumps(role_users), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None - ## INDIVIDUAL ACTIONS + # INDIVIDUAL ACTIONS @app.json_route("/ddapi/user", methods=["POST"]) @app.json_route("/ddapi/user/", methods=["PUT", "GET", "DELETE"]) @has_token - def ddapi_user(user_ddid : Optional[str]=None) -> OptionalJsonResponse: - uid : str = user_ddid if user_ddid else '' - if request.method == "GET": - user = app.admin.get_user_username(uid) - if not user: - raise Error("not_found", "User id not found") - return json.dumps(user_parser(user)), 200, {"Content-Type": "application/json"} - if request.method == "DELETE": - user = app.admin.get_user_username(uid) - if not user: - raise Error("not_found", "User id not found") - app.admin.delete_user(user["id"]) - return json.dumps({}), 200, {"Content-Type": "application/json"} - if request.method == "POST": - data = request.get_json(force=True) - if not app.validators["user"].validate(data): - raise Error( - "bad_request", - "Data validation for user failed: " - + str(app.validators["user"].errors), - traceback.format_exc(), + def ddapi_user(user_ddid: Optional[str] = None) -> OptionalJsonResponse: + try: + uid: str = user_ddid if user_ddid else "" + if request.method == "POST": + if uid: + return ERR_400 + try: + data = request.get_json(force=True) + if not app.validators["user"].validate(data): + log.debug( + "Data validation for user failed: " + + str(app.validators["user"].errors) + ) + log.debug(traceback.format_exc()) + return ERR_400 + except Exception: + return ERR_400 + + if app.admin.get_user_username(data["username"]): + return ERR_409 + data = app.validators["user"].normalized(data) + try: + keycloak_id = app.admin.add_user(data) + if not keycloak_id: + # Group does not exist already + return ERR_412 + except Error as e: + if e.error.get("error") == "conflict": + # It already exists + return ERR_409 + log.error(traceback.format_exc()) + return ERR_500 + return ( + json.dumps({"keycloak_id": keycloak_id}), + 200, + {"Content-Type": "application/json"}, ) - if app.admin.get_user_username(data["username"]): - raise Error("conflict", "User id already exists") - data = app.validators["user"].normalized(data) - keycloak_id = app.admin.add_user(data) - if not keycloak_id: - raise Error( - "precondition_required", - "Not all user groups already in system. Please create user groups before adding user.", + if request.method == "GET": + user = app.admin.get_user_username(uid) + if not user: + return ERR_404 + return ( + json.dumps(user_parser(user)), + 200, + {"Content-Type": "application/json"}, ) - return ( - json.dumps({"keycloak_id": keycloak_id}), - 200, - {"Content-Type": "application/json"}, - ) + if request.method == "DELETE": + user = app.admin.get_user_username(uid) + if not user: + return ERR_404 + app.admin.delete_user(user["id"]) + return json.dumps({}), 200, {"Content-Type": "application/json"} - if request.method == "PUT": - user = app.admin.get_user_username(uid) - if not user: - raise Error("not_found", "User id not found") - data = request.get_json(force=True) - if not app.validators["user_update"].validate(data): - raise Error( - "bad_request", - "Data validation for user failed: " - + str(app.validators["user_update"].errors), - traceback.format_exc(), - ) - data = {**user, **data} - data = app.validators["user_update"].normalized(data) - data = {**data, **{"username": uid}} - data["roles"] = [data.pop("role")] - data["firstname"] = data.pop("first") - data["lastname"] = data.pop("last") - app.admin.user_update(data) - if data.get("password"): - app.admin.user_update_password( - user["id"], data["password"], data["password_temporary"] - ) - return json.dumps({}), 200, {"Content-Type": "application/json"} + if request.method == "PUT": + user = app.admin.get_user_username(uid) + if not user: + return ERR_404 + try: + data = request.get_json(force=True) + if not data or not app.validators["user_update"].validate(data): + log.debug( + "Data validation for user failed: " + + str(app.validators["user_update"].errors) + ) + log.debug(traceback.format_exc()) + return ERR_400 + except Exception: + return ERR_400 + data = app.validators["user_update"].normalized(data) + # Work with a secure copy + u = copy.deepcopy(user) + u.update({"username": uid}) + # Update object from API-provided data + for p in ["email", "quota", "enabled", "groups"]: + if p in data: + u[p] = data[p] + for lp, rp in [("firstname", "first"), ("lastname", "last")]: + if rp in data: + u[lp] = data[rp] + # And role + if "role" in data: + u["roles"] = [data["role"]] + app.admin.user_update(u) + if data.get("password"): + app.admin.user_update_password( + user["id"], data["password"], data["password_temporary"] + ) + return json.dumps({}), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/username//", methods=["PUT"]) @has_token - def ddapi_username(old_user_ddid : str, new_user_did : str) -> OptionalJsonResponse: - user = app.admin.get_user_username(old_user_ddid) - if not user: - raise Error("not_found", "User id not found") - # user = app.admin.update_user_username(old_user_ddid,new_user_did) - return json.dumps("Not implemented yet!"), 419, {"Content-Type": "application/json"} + def ddapi_username(old_user_ddid: str, new_user_did: str) -> OptionalJsonResponse: + try: + user = app.admin.get_user_username(old_user_ddid) + if not user: + return ERR_404 + # user = app.admin.update_user_username(old_user_ddid,new_user_did) + return ERR_501 + except Exception: + log.error(traceback.format_exc()) + return ERR_500 @app.json_route("/ddapi/group", methods=["POST"]) @app.json_route("/ddapi/group/", methods=["GET", "POST", "DELETE"]) # @app.json_route("/api/group/", methods=["PUT", "GET", "DELETE"]) @has_token - def ddapi_group(group_id : Optional[str]=None) -> OptionalJsonResponse: - uid : str = group_id if group_id else '' - if request.method == "GET": - group = app.admin.get_group_by_name(uid) - if not group: - Error("not found", "Group id not found") - return ( - json.dumps(group_parser(group)), - 200, - {"Content-Type": "application/json"}, - ) - if request.method == "POST": - data = request.get_json(force=True) - if not app.validators["group"].validate(data): - raise Error( - "bad_request", - "Data validation for group failed: " - + str(app.validators["group"].errors), - traceback.format_exc(), + def ddapi_group(group_id: Optional[str] = None) -> OptionalJsonResponse: + try: + uid: str = group_id if group_id else "" + # /ddapi/group + if request.method == "POST": + try: + data = request.get_json(force=True) + if not app.validators["group"].validate(data): + log.debug( + "Data validation for group failed: " + + str(app.validators["group"].errors) + ) + log.debug(traceback.format_exc()) + return ERR_400 + except Exception: + return ERR_400 + data = app.validators["group"].normalized(data) + data["parent"] = data["parent"] if data["parent"] != "" else None + + if app.admin.get_group_by_name(uid): + return ERR_409 + + app.admin.add_group(data) + return ( + json.dumps({"keycloak_id": None}), + 200, + {"Content-Type": "application/json"}, ) - data = app.validators["group"].normalized(data) - data["parent"] = data["parent"] if data["parent"] != "" else None - - if app.admin.get_group_by_name(uid): - raise Error("conflict", "Group id already exists") - - path = app.admin.add_group(data) - # log.error(path) - # keycloak_id = app.admin.get_group_by_name(id)["id"] - # log.error() - return ( - json.dumps({"keycloak_id": None}), - 200, - {"Content-Type": "application/json"}, - ) - if request.method == "DELETE": - group = app.admin.get_group_by_name(uid) - if not group: - raise Error("not_found", "Group id not found") - app.admin.delete_group_by_id(group["id"]) - return json.dumps({}), 200, {"Content-Type": "application/json"} + # /ddapi/group/ + if request.method == "GET": + group = app.admin.get_group_by_name(uid) + if not group: + return ERR_404 + return ( + json.dumps(group_parser(group)), + 200, + {"Content-Type": "application/json"}, + ) + if request.method == "DELETE": + group = app.admin.get_group_by_name(uid) + if not group: + return ERR_404 + app.admin.delete_group_by_id(group["id"]) + return json.dumps({}), 200, {"Content-Type": "application/json"} + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None @app.json_route("/ddapi/user_mail", methods=["POST"]) @app.json_route("/ddapi/user_mail/", methods=["GET", "DELETE"]) @has_token - def ddapi_user_mail(id : Optional[str]=None) -> OptionalJsonResponse: - # TODO: Remove this endpoint when we ensure there are no consumers - if request.method == "GET": - return ( - json.dumps("Not implemented yet"), - 200, - {"Content-Type": "application/json"}, - ) - if request.method == "POST": - data = request.get_json(force=True) + def ddapi_user_mail(id: Optional[str] = None) -> OptionalJsonResponse: + try: + # TODO: Remove this endpoint when we ensure there are no consumers + if request.method in ["GET", "DELETE"]: + return ERR_501 + if request.method == "POST": + try: + data = request.get_json(force=True) + assert isinstance(data, list) and data + for user in data: + if not app.validators["mail"].validate(user): + log.debug( + "Data validation for mail failed: " + + str(app.validators["mail"].errors) + ) + log.debug(traceback.format_exc()) + return ERR_400 + except Exception: + return ERR_400 - # if not app.validators["mails"].validate(data): - # raise Error( - # "bad_request", - # "Data validation for mail failed: " - # + str(app.validators["mail"].errors), - # traceback.format_exc(), - # ) - for user in data: - if not app.validators["mail"].validate(user): - raise Error( - "bad_request", - "Data validation for mail failed: " - + str(app.validators["mail"].errors), - traceback.format_exc(), - ) - for user in data: - log.info("Added user email") - app.admin.nextcloud_mail_set([user], dict()) - return ( - json.dumps("Users emails updated"), - 200, - {"Content-Type": "application/json"}, - ) + for user in data: + log.info("Added user email") + app.admin.nextcloud_mail_set([user], dict()) + return ( + json.dumps("Users emails updated"), + 200, + {"Content-Type": "application/json"}, + ) + except Exception: + log.error(traceback.format_exc()) + return ERR_500 return None + +def role_parser(role: Dict[str, str]) -> Dict[str, Any]: + return { + "keycloak_id": role["id"], + "id": role["name"], + "name": role["name"], + "description": role.get("description", ""), + } + + # TODO: After this line, this is all mostly duplicated from other places... -def user_parser(user : Dict[str, Any]) -> Dict[str, Any]: +def user_parser(user: Dict[str, Any]) -> Dict[str, Any]: return { "keycloak_id": user["id"], "id": user["username"], @@ -346,7 +400,7 @@ def user_parser(user : Dict[str, Any]) -> Dict[str, Any]: } -def group_parser(group : Dict[str, str]) -> Dict[str, Any]: +def group_parser(group: Dict[str, str]) -> Dict[str, Any]: return { "keycloak_id": group["id"], "id": group["name"], @@ -356,7 +410,7 @@ def group_parser(group : Dict[str, str]) -> Dict[str, Any]: } -def filter_users(users : Iterable[Dict[str, Any]], text : str) -> List[Dict[str, Any]]: +def filter_users(users: Iterable[Dict[str, Any]], text: str) -> List[Dict[str, Any]]: return [ user for user in users diff --git a/dd-sso/admin/src/admin/views/test/__init__.py b/dd-sso/admin/src/admin/views/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dd-sso/admin/src/admin/views/test/mocks.py b/dd-sso/admin/src/admin/views/test/mocks.py new file mode 100644 index 0000000..dc053d7 --- /dev/null +++ b/dd-sso/admin/src/admin/views/test/mocks.py @@ -0,0 +1,243 @@ +""" +Mocks to test API views. +""" +import copy +from typing import Any, Dict, Iterable, List, Optional, cast + +from admin.flaskapp import AdminFlaskApp +from admin.lib.admin import Admin +from admin.lib.keycloak_client import KeycloakClient + + +class MockKeycloakAdmin: + app: AdminFlaskApp + + def __init__(self, app: AdminFlaskApp) -> None: + self.app = app + + def _convert_kc(self, data: Dict[str, Any]) -> Dict[str, Any]: + u = { + "email": data["email"], + "id": data["id"], + "username": data["username"], + "first": data["firstName"], + "last": data["lastName"], + "enabled": data["enabled"], + "roles": [], + } + return u + + def create_user(self, data: Dict[str, Any]) -> Any: + uid = data.get("username") + us = {u["id"]: u for u in self.app.admin.internal["users"]} + if not uid or uid in us: + raise Exception("Invalid or existing user") + u = copy.deepcopy(data) + u["id"] = uid + u = self._convert_kc(u) + self.app.admin.internal["users"].append(u) + return uid + + def group_user_add(self, user_id: str, group_id: str) -> Any: + o = {u["id"]: u for u in self.app.admin.internal["users"]}.get(user_id, {}) + o["groups"] = list(set(o.get("groups", []) + [group_id])) + return o + + def delete_user(self, user_id: str) -> None: + self.app.admin.internal["users"] = [ + u for u in self.app.admin.internal["users"] if u["id"] != user_id + ] + + def get_groups(self) -> List[Dict[str, Any]]: + return cast(List[Dict[str, Any]], self.app.admin.internal["groups"]) + + def get_realm_roles_of_user(self, user_id: str) -> List[Dict[str, Any]]: + o = {u["id"]: u for u in self.app.admin.internal["users"]}.get(user_id, {}) + return [{"name": r} for r in o.get("roles", [])] + + def get_group(self, group_id: str) -> Dict[str, Any]: + o = {g["id"]: g for g in self.app.admin.internal["groups"]}.get(group_id, {}) + return cast(Dict[str, Any], o) + + def create_group(self, group: Dict[str, Any], parent: Optional[str] = None) -> Any: + g = copy.deepcopy(group) + g.update( + { + "id": g["name"], + "path": "/".join(["", parent, g["name"]]) if parent else "", + } + ) + self.app.admin.internal["groups"].append(g) + + def delete_user_realm_role(self, user_id: str, roles: str) -> None: + o = {u["id"]: u for u in self.app.admin.internal["users"]}.get(user_id, {}) + o["roles"] = [r for r in o.get("roles", []) if r not in roles] + o["keycloak_groups"] = o["roles"] + + def update_user(self, user_id: str, payload: Dict[str, Any]) -> Any: + pass + + +class MockKeycloak(KeycloakClient): + app: AdminFlaskApp + + def __init__(self, app: AdminFlaskApp) -> None: + self.app = app + self.keycloak_admin = MockKeycloakAdmin(app) + + def get_group_by_path(self, path: str, recursive: bool = True) -> Any: + gs = {g["id"]: g for g in self.app.admin.internal["groups"]} + return gs.get(path.lstrip("/")) + + def assign_realm_roles(self, uid: str, role: str) -> Dict[str, Any]: + o: Dict[str, Any] = {u["id"]: u for u in self.app.admin.internal["users"]}.get( + uid, {} + ) + o["roles"] = list(set(o.get("roles", []) + [role])) + o["keycloak_groups"] = o["roles"] + return o + + def connect(self) -> None: + pass + + def user_update( + self, + user_id: str, + enabled: bool, + email: str, + first: str, + last: str, + groups: Iterable[str] = [], + roles: Iterable[str] = [], + ) -> Dict[str, Any]: + o: Dict[str, Any] = {u["id"]: u for u in self.app.admin.internal["users"]}.get( + user_id, {} + ) + o.update({"first": first, "last": last, "enabled": enabled}) + o["groups"] = list(sorted(o["groups"])) + return o + + def delete_group(self, group_id: str) -> None: + self.app.admin.internal["groups"] = [ + g for g in self.app.admin.internal["groups"] if g["id"] != group_id + ] + + +class MockMoodle: + app: AdminFlaskApp + + def __init__(self, app: AdminFlaskApp): + self.app = app + + def create_user( + self, + email: str, + username: str, + password: str, + first_name: str = "-", + last_name: str = "-", + ) -> List[Dict[str, Any]]: + o = {u["id"]: u for u in self.app.admin.internal["users"]}.get(username, {}) + o["moodle"] = True + o["moodle_id"] = username + return [{"id": username, "username": username}] + + def get_cohorts(self) -> List[Dict[str, Any]]: + return cast(List[Dict[str, Any]], self.app.admin.internal["groups"]) + + def add_user_to_cohort(self, userid: str, cohortid: str) -> List[Dict[str, Any]]: + return [{"id": userid, "username": userid}] + + def delete_users(self, user_id: str) -> None: + for u in user_id: + self.app.admin.delete_user(user_id) + + def update_user( + self, + username: str, + email: str, + first_name: str, + last_name: str, + enabled: bool = True, + ) -> None: + pass + + def add_system_cohort( + self, name: str, description: str, visible: bool = True + ) -> Dict[str, Any]: + return {"name": name} + + def delete_cohorts(self, group_id: str) -> None: + pass + + +class MockNextcloud: + app: AdminFlaskApp + + def __init__(self, app: AdminFlaskApp): + self.app = app + + # nextcloud + def add_user_with_groups( + self, + userid: str, + userpassword: str, + quota: Any = False, + groups: Any = [], + email: str = "", + displayname: str = "", + ) -> bool: + o = {u["id"]: u for u in self.app.admin.internal["users"]}.get(userid, {}) + o["quota"] = quota + o["quota_used_bytes"] = 0 + return True + + def delete_user(self, userid: str) -> None: + pass + + def update_user(self, user_id: str, user: Dict[str, Any]) -> Any: + pass + + def add_user_to_group(self, user_id: str, group_id: str) -> bool: + return True + + def add_group(self, groupid: str) -> bool: + return True + + def delete_group(self, group_id: str) -> None: + pass + + +class MockAdmin(Admin): + """ + Mock class to easily test the API views. + """ + + app: AdminFlaskApp + internal: Dict[str, Any] + + def __init__(self, app: AdminFlaskApp): + """ + Constructor override so we can use the class for testing + """ + self.app = app + self.av = self # type: ignore + self.moodle = MockMoodle(app) # type: ignore + self.nextcloud = MockNextcloud(app) # type: ignore + self.third_party_cbs = [] + # Initialise data + self.internal = { + "users": [], + "groups": [], + "roles": [], + } + + def resync_data(self) -> bool: + return True + + # avatars + def add_user_default_avatar(self, uid: str, role: str) -> None: + pass + + def delete_user_avatar(self, uid: str) -> None: + pass diff --git a/dd-sso/admin/src/admin/views/test/test_ApiViews.py b/dd-sso/admin/src/admin/views/test/test_ApiViews.py new file mode 100644 index 0000000..8b98d76 --- /dev/null +++ b/dd-sso/admin/src/admin/views/test/test_ApiViews.py @@ -0,0 +1,555 @@ +""" +""" + +import copy +import os +from tempfile import TemporaryDirectory +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import flask_unittest +from flask.testing import FlaskClient +from jose import jws + +if TYPE_CHECKING: + from werkzeug.test import TestResponse + +from admin.flaskapp import AdminFlaskApp +from admin.lib.admin import MANAGER, STUDENT, TEACHER +from admin.views.ApiViews import group_parser, role_parser, user_parser +from admin.views.test.mocks import MockAdmin, MockKeycloak + + +def _testApp() -> AdminFlaskApp: + """ + Helper function to generate a testableFlask App + """ + app = AdminFlaskApp( + "TestApp", root_path="src/admin", template_folder="static/templates" + ) + # Patch the admin + app.admin = MockAdmin(app) + # Patch keycloak client + app.admin.keycloak = MockKeycloak(app) + # Add socketio + from flask_socketio import SocketIO + + app.socketio = SocketIO(app) + return app + + +class ApiViewsTests(flask_unittest.ClientTestCase): + # + # Instance fields + # + _app: Optional[AdminFlaskApp] = None + secret: str = "TestSecret" + tmpdir: TemporaryDirectory + + # + # Instance properties + # + @property + def app(self) -> AdminFlaskApp: + if not self._app: + os.environ["DOMAIN"] = "dd-test.example" + os.environ["API_SECRET"] = self.secret + self._app = _testApp() + return self._app + + @property + def auth_header(self) -> Dict[str, str]: + token = jws.sign({}, self.secret, algorithm="HS256") + return {"Authorization": f"bearer {token}"} + + # + # Test setup + # + def setUp(self, client: FlaskClient) -> None: + self.tmpdir = TemporaryDirectory() + os.environ["NC_MAIL_QUEUE_FOLDER"] = os.path.join( + str(self.tmpdir.name), "nc_queue" + ) + + def tearDown(self, client: FlaskClient) -> None: + self.tmpdir.cleanup() + self._app = None + + # + # Helper functions + # + def _ur( + self, + client: FlaskClient, + route: str, + method: str = "GET", + response_code: int = 401, + ) -> "TestResponse": + """ + Helper function to ensure endpoints without authenticating + """ + rv = client.open(route, method=method) + self.assertStatus(rv, response_code) + return rv + + def _r( + self, + client: FlaskClient, + route: str, + method: str = "GET", + response_code: int = 200, + *args: Any, + **kwargs: Any, + ) -> "TestResponse": + """ + Helper function to test endpoint functionality with authentication + """ + rv = client.open( + route, method=method, headers=self.auth_header, *args, **kwargs + ) + self.assertStatus(rv, response_code) + return rv + + _bad_json: List[Any] = [None, [], "test", 1, 0, True, False, {}] + + _initialUsers = [ + { + "username": "u1", + "id": "u1", + "keycloak": True, + "keycloak_id": "u1", + "enabled": True, + "email": "u1@u.com", + "first": "u", + "firstname": "u", + "last": "1", + "lastname": "1", + "roles": [MANAGER], + "keycloak_groups": ["group1"], + "groups": ["group1", MANAGER], + "moodle": True, + "moodle_id": "u1", + "moodle_groups": [], + "nextcloud": True, + "nextcloud_id": "u1", + "nextcloud_groups": [], + "quota": False, + "quota_used_bytes": False, + }, + ] + + _initialGroups = [ + {"keycloak": True, "id": r, "name": r, "path": r} + for r in ["group1", MANAGER, STUDENT, TEACHER] + ] + _initialRoles = [ + {"id": r, "name": f"NAME {r}", "description": f"DESC {r}"} + for r in [MANAGER, STUDENT, TEACHER] + ] + + def _feedInitialData(self) -> None: + self.app.admin.internal["users"] = copy.deepcopy(self._initialUsers) + self.app.admin.internal["groups"] = copy.deepcopy(self._initialGroups) + self.app.admin.internal["roles"] = copy.deepcopy(self._initialRoles) + + def _canonicUserData(self, users: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return [user_parser(u) for u in users] + + def _canonicGroupData(self, groups: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return [group_parser(g) for g in groups] + + def _canonicRoleData(self, roles: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return [role_parser(r) for r in roles] + + # + # Actual tests + # + + # /ddapi/users + def test_users_401(self, client: FlaskClient) -> None: + for m in ["GET"]: + self._ur(client, "/ddapi/users", method=m) + + def test_users_405(self, client: FlaskClient) -> None: + for m in ["PUT", "DELETE", "POST"]: + self._ur(client, "/ddapi/users", method=m, response_code=405) + self._r(client, "/ddapi/users", method=m, response_code=405) + + def test_users_GET(self, client: FlaskClient) -> None: + """Ensure GETting users makes sense""" + rv = self._r(client, "/ddapi/users") + # When empty, it should return an empty list + self.assertEqual([], rv.json, msg="Expected empty list") + + # When populated, it should return the test data + self._feedInitialData() + rv = self._r(client, "/ddapi/users") + self.assertEqual( + self._canonicUserData(self._initialUsers), + rv.json, + msg="Unexpected data received", + ) + + # /ddapi/users/filter + # Searches in username, first, last, email + def test_users_filter_401(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/users/filter", method=m) + + def test_users_filter_405(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "PUT"]: + self._ur(client, "/ddapi/users/filter", method=m, response_code=405) + self._r(client, "/ddapi/users/filter", method=m, response_code=405) + + def test_users_filter_POST(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/users/filter", method="POST", response_code=400) + + for bad_j in self._bad_json: + rv = self._r( + client, + "/ddapi/users/filter", + method="POST", + response_code=400, + json=bad_j, + ) + + rv = self._r(client, "/ddapi/users/filter", method="POST", json={"text": "u1"}) + self.assertEqual([], rv.json, "Unexpected data received") + self._feedInitialData() + rv = self._r(client, "/ddapi/users/filter", method="POST", json={"text": "u1"}) + self.assertEqual( + self._canonicUserData(self._initialUsers), + rv.json, + "Unexpected data received", + ) + rv = self._r( + client, + "/ddapi/users/filter", + method="POST", + json={"text": "SEARCHING_FOR_NO_MATCH"}, + ) + self.assertEqual([], rv.json, "Unexpected data received") + + # /ddapi/groups + def test_groups_401(self, client: FlaskClient) -> None: + for m in ["GET"]: + self._ur(client, "/ddapi/groups", method=m) + + def test_groups_405(self, client: FlaskClient) -> None: + for m in ["POST", "PUT", "DELETE"]: + self._ur(client, "/ddapi/groups", method=m, response_code=405) + self._r(client, "/ddapi/groups", method=m, response_code=405) + + def test_groups_GET(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/groups") + self.assertEqual([], rv.json, "Unexpected data received") + self._feedInitialData() + rv = self._r(client, "/ddapi/groups") + self.assertEqual( + self._canonicGroupData(self._initialGroups), + rv.json, + "Unexpected data received", + ) + + # /ddapi/roles + def test_roles_401(self, client: FlaskClient) -> None: + for m in ["GET"]: + self._ur(client, "/ddapi/roles", method=m) + + def test_roles_405(self, client: FlaskClient) -> None: + for m in ["POST", "DELETE", "PUT"]: + self._ur(client, "/ddapi/roles", method=m, response_code=405) + self._r(client, "/ddapi/roles", method=m, response_code=405) + + def test_roles_GET(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/roles") + self.assertEqual([], rv.json, "Unexpected data received") + self._feedInitialData() + rv = self._r(client, "/ddapi/roles") + self.assertEqual( + self._canonicRoleData(self._initialRoles), + rv.json, + "Unexpected data received", + ) + + # /ddapi/role/users + # - id|name|keycloak_id <-- used in this order, equivalent + def test_role_users_405(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "PUT"]: + self._ur(client, "/ddapi/role/users", method=m, response_code=405) + self._r(client, "/ddapi/role/users", method=m, response_code=405) + + def test_role_users_401(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/role/users", method=m) + + def test_role_users_POST(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/role/users", method="POST", response_code=400) + for bad_j in self._bad_json: + rv = self._r( + client, + "/ddapi/role/users", + method="POST", + response_code=400, + json=bad_j, + ) + rv = self._r(client, "/ddapi/role/users", method="POST", json={"id": MANAGER}) + self.assertEqual([], rv.json, "Unexpected data received") + self._feedInitialData() + rv = self._r(client, "/ddapi/role/users", method="POST", json={"id": MANAGER}) + self.assertEqual( + self._canonicUserData(self._initialUsers), + rv.json, + "Unexpected data received", + ) + rv = self._r(client, "/ddapi/role/users", method="POST", json={"id": TEACHER}) + self.assertEqual([], rv.json, "Unexpected data received") + + # /ddapi/user[/] + def test_user_401(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/user", method=m) + + def test_user_405(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "PUT"]: + self._ur(client, "/ddapi/user", method=m, response_code=405) + self._r(client, "/ddapi/user", method=m, response_code=405) + + def test_user_POST(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user", method="POST", response_code=400) + for bad_j in self._bad_json: + self._r(client, "/ddapi/user", method="POST", response_code=400, json=bad_j) + new_user = { + "email": "a", + "enabled": True, + "password": "PASS", + "quota": "", + "username": "cu1", + "first": "FIRSTNAME", + "last": "LASTNAME", + "role": MANAGER, + "groups": ["group2"], + } + self._r(client, "/ddapi/user", method="POST", response_code=400, json=new_user) + self.assertNotIn( + "cu1", [u["id"] for u in self.app.admin.internal["users"]], "Created user" + ) + new_user["email"] = "a@a.example" + self._r(client, "/ddapi/user", method="POST", response_code=412, json=new_user) + self.assertNotIn( + "cu1", [u["id"] for u in self.app.admin.internal["users"]], "Created user" + ) + self._feedInitialData() + self._r(client, "/ddapi/user", method="POST", response_code=412, json=new_user) + self.assertNotIn( + "cu1", [u["id"] for u in self.app.admin.internal["users"]], "Created user" + ) + new_user["groups"] = ["group1"] + self._r(client, "/ddapi/user", method="POST", response_code=200, json=new_user) + self.assertIn( + "cu1", + [u["id"] for u in self.app.admin.internal["users"]], + "Did not create user", + ) + + def test_user_ddid_GET_401(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "PUT"]: + self._ur(client, "/ddapi/user/u1", method=m) + + def test_user_ddid_GET_405(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/user/u1", method=m, response_code=405) + self._r(client, "/ddapi/user/u1", method=m, response_code=405) + + def test_user_ddid_GET(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user/", response_code=405) + self._r(client, "/ddapi/user/u1", response_code=404) + self._feedInitialData() + rv = self._r(client, "/ddapi/user/u1", response_code=200) + self.assertEqual("u1", rv.json["username"]) + + def test_user_ddid_DELETE(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user/u1", method="DELETE", response_code=404) + self._feedInitialData() + rv = self._r(client, "/ddapi/users") + self.assertNotEqual(rv.json, []) + self._r(client, "/ddapi/user/u1", method="DELETE") + rv = self._r(client, "/ddapi/users") + self.assertEqual(rv.json, []) + rv = self._r(client, "/ddapi/user/u1", response_code=404) + + def test_user_ddid_PUT(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user/u1", method="PUT", response_code=404) + self._feedInitialData() + rv = self._r(client, "/ddapi/user/u1") + prev_u = rv.json + self.assertEqual(prev_u["first"], "u") + self._r(client, "/ddapi/user/u1", method="PUT", response_code=400) + for bad_j in self._bad_json: + self._r( + client, "/ddapi/user/u1", method="PUT", response_code=400, json=bad_j + ) + rv = self._r(client, "/ddapi/user/u1") + self.assertEqual(prev_u, rv.json) + self._r( + client, + "/ddapi/user/u1", + method="PUT", + response_code=200, + json={"first": "U"}, + ) + rv = self._r(client, "/ddapi/user/u1") + new_u = rv.json + self.assertNotEqual(prev_u, rv.json) + self.assertEqual(rv.json["first"], "U") + prev_u = new_u + self._r( + client, + "/ddapi/user/u1", + method="PUT", + response_code=200, + json={"first": "U", "password": "TEST2"}, + ) + + # /ddapi/username/OLD/NEW + def test_username_401(self, client: FlaskClient) -> None: + for m in ["PUT"]: + self._ur(client, "/ddapi/username/a/b", method=m) + + def test_username_405(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "POST"]: + self._ur(client, "/ddapi/username/a/b", method=m, response_code=405) + self._r(client, "/ddapi/username/a/b", method=m, response_code=405) + + def test_username_PUT(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/users") + self.assertEqual([], rv.json) + self._r(client, "/ddapi/username/u1/u2", method="PUT", response_code=404) + self._feedInitialData() + rv = self._r(client, "/ddapi/users") + self.assertIn("u1", [u["id"] for u in rv.json]) + self._r(client, "/ddapi/username/u1/u2", method="PUT", response_code=501) + + # /ddapi/group + # /ddapi/group/ + def test_group_401(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/group", method=m) + + def test_group_405(self, client: FlaskClient) -> None: + for m in ["GET", "PUT", "DELETE"]: + self._ur(client, "/ddapi/group", method=m, response_code=405) + self._r(client, "/ddapi/group", method=m, response_code=405) + + def test_group_POST(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/group", method="POST", response_code=400) + for bad_j in self._bad_json: + self._r( + client, "/ddapi/group", method="POST", response_code=400, json=bad_j + ) + rv = self._r(client, "/ddapi/groups") + self.assertEqual([], rv.json) + self._r(client, "/ddapi/group", method="POST", json={"name": "group2"}) + rv = self._r(client, "/ddapi/groups") + self.assertIn("group2", [g["name"] for g in rv.json]) + # TODO: Check what this is supposed to do + # self._r(client, "/ddapi/group", method='POST', response_code=409, + # json={"name": "group2"}) + + def test_group_gid_401(self, client: FlaskClient) -> None: + for m in ["GET", "POST", "DELETE"]: + self._ur(client, "/ddapi/group/group2", method=m) + + def test_group_gid_405(self, client: FlaskClient) -> None: + for m in ["PUT"]: + self._ur(client, "/ddapi/group/group2", method=m, response_code=405) + self._r(client, "/ddapi/group/group2", method=m, response_code=405) + + def test_group_gid_GET(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/group/group2", response_code=404) + self._r(client, "/ddapi/group", method="POST", json={"name": "group2"}) + rv = self._r(client, "/ddapi/group/group2") + self.assertEqual("group2", rv.json["name"]) + + def test_group_gid_POST(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/group/group2", method="POST", response_code=400) + for bad_j in self._bad_json: + self._r( + client, + "/ddapi/group/group2", + method="POST", + response_code=400, + json=bad_j, + ) + self._r(client, "/ddapi/group/group2", response_code=404) + self._r(client, "/ddapi/group/group2", method="POST", json={"name": "group2"}) + rv = self._r(client, "/ddapi/groups") + self.assertIn("group2", [g["name"] for g in rv.json]) + rv = self._r(client, "/ddapi/group/group2") + self.assertEqual("group2", rv.json["name"]) + self._r( + client, + "/ddapi/group/group2", + method="POST", + response_code=409, + json={"name": "group2"}, + ) + + def test_group_gid_DELETE(self, client: FlaskClient) -> None: + rv = self._r(client, "/ddapi/groups") + self.assertEqual([], rv.json) + self._r(client, "/ddapi/group/group2", method="DELETE", response_code=404) + self._r(client, "/ddapi/group/group2", method="POST", json={"name": "group2"}) + rv = self._r(client, "/ddapi/groups") + self.assertIn("group2", [g["name"] for g in rv.json]) + self._r(client, "/ddapi/group/group2", method="DELETE", response_code=200) + rv = self._r(client, "/ddapi/groups") + self.assertEqual([], rv.json) + + # /ddapi/user_mail + # /ddapi/user_mail/ + def test_user_mail_401(self, client: FlaskClient) -> None: + for m in ["POST"]: + self._ur(client, "/ddapi/user_mail", method=m) + + def test_user_mail_405(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE", "PUT"]: + self._ur(client, "/ddapi/user_mail", method=m, response_code=405) + self._r(client, "/ddapi/user_mail", method=m, response_code=405) + + def test_user_mail_POST(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user_mail", method="POST", response_code=400) + for bad_j in self._bad_json: + self._r( + client, "/ddapi/user_mail", method="POST", response_code=400, json=bad_j + ) + us = [{"user_id": "u1", "email": "a"}] + self._r(client, "/ddapi/user_mail", method="POST", response_code=400, json=us) + + def test_user_mail_id_401(self, client: FlaskClient) -> None: + for m in ["GET", "DELETE"]: + self._ur(client, "/ddapi/user_mail/id", method=m) + + def test_user_mail_id_405(self, client: FlaskClient) -> None: + for m in ["POST", "PUT"]: + self._ur(client, "/ddapi/user_mail/id", method=m, response_code=405) + self._r(client, "/ddapi/user_mail/id", method=m, response_code=405) + + def test_user_mail_id_GET(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user_mail/u1", response_code=501) + + def test_user_mail_id_DELETE(self, client: FlaskClient) -> None: + self._r(client, "/ddapi/user_mail/u1", method="DELETE", response_code=501) + + # /ddapi/group/users + # --> deleted code, was never used + # def test_group_users_GET_unauth(self, client: FlaskClient) -> None: + # return self._ur( + # client, "/ddapi/group/users" + # ) + # def test_group_users_GET(self, client: FlaskClient) -> None: + # rv = self._r(client, "/ddapi/group/users", response_code=405) + + # def test_get_role_users(self, client: FlaskClient) -> None: + # rv = self._r(client, "/ddapi/role/users", method="POST") + # print(rv) + # print(rv.json)