15 Commits

Author SHA1 Message Date
Tobias K.
466cb344e6 using poetry now 2023-10-24 15:39:29 +02:00
3ae6db1184 virtual_command_model changes -> model parser needs modification -> also the model relations between recorden model and virtual command may be removed \(not done yet\) 2020-08-14 15:59:24 +02:00
b18f9ba616 re-enabled localhost server name 2020-08-13 16:14:38 +02:00
Tobias Kurze
c8a517ff60 changes to run configuration and virtual command model 2020-08-13 09:00:54 +02:00
Tobias Kurze
9c19708381 now using campus API 2020-08-11 16:39:13 +02:00
160076afcf slight changes to config 2020-08-07 16:47:33 +02:00
Tobias Kurze
437cec38e0 added permission checks to user and recorder API 2020-08-06 15:23:14 +02:00
Tobias Kurze
82b3e78488 improved register endpoint, etc. 2020-08-05 16:36:14 +02:00
Tobias Kurze
16e4231807 now returning bad request when wrong parameters are supplied for room / recorder creation 2020-08-05 07:51:13 +02:00
dc142bca0c some changes to virtual command api, but its not yet clear how this should function 2020-07-31 16:33:02 +02:00
Tobias Kurze
cc334f1727 oidc working again (getting less info to limit cookie size) 2020-07-28 15:09:08 +02:00
de398d189a tryd to fix a oid connect related bug, but there is still a BIG problem 2020-07-24 16:45:18 +02:00
Tobias Kurze
1d4c4c8ec2 not sending previous state if null anymore 2020-07-21 15:37:54 +02:00
Tobias Kurze
6b50334741 Merge branch 'master' of git.scc.kit.edu:sg8018/lrc-backend 2020-07-20 17:52:04 +02:00
Tobias Kurze
bd8e60bf5d state api now working 2020-07-20 17:51:57 +02:00
43 changed files with 3759 additions and 495 deletions

View File

@@ -39,6 +39,7 @@ pillow = "*"
pydub = "*"
simpleaudio = "*"
flask-restx = "*"
cac = {git = "https://git.scc.kit.edu/sg8018/campus_api_client"}
[dev-packages]

640
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "a58778e12f8a5bcff82fd344f97bf22ed1917412e66289d13be718fd88cfc64a"
"sha256": "12e20b3ff7868a6e8c6bd6781acb0deaed105ba25bc22ede80660c47f07c940b"
},
"pipfile-spec": 6,
"requires": {
@@ -18,9 +18,10 @@
"default": {
"alembic": {
"hashes": [
"sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26"
"sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf"
],
"version": "==1.4.0"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.4.2"
},
"aniso8601": {
"hashes": [
@@ -42,6 +43,7 @@
"sha256:4bfacea734ead51495dc47df00421ecfd4ca1f2c0fbe58b9a26eaeddedc31caf",
"sha256:67f8be7c0cf420424bc62d8d7dc40b44e4bb2f7b515f9cc2954fb36e35797656"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.14.7"
},
"attrs": {
@@ -49,6 +51,7 @@
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"beaker": {
@@ -59,52 +62,56 @@
},
"beautifulsoup4": {
"hashes": [
"sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a",
"sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887",
"sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae"
"sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7",
"sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8",
"sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"
],
"index": "pypi",
"version": "==4.8.2"
"version": "==4.9.1"
},
"cac": {
"git": "https://git.scc.kit.edu/sg8018/campus_api_client",
"ref": "735b763284408c34be7803315829dc54ab9472a8"
},
"certifi": {
"hashes": [
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
"version": "==2019.11.28"
"version": "==2020.6.20"
},
"cffi": {
"hashes": [
"sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff",
"sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b",
"sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac",
"sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0",
"sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384",
"sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26",
"sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6",
"sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b",
"sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e",
"sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd",
"sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2",
"sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66",
"sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc",
"sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8",
"sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55",
"sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4",
"sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5",
"sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d",
"sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78",
"sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa",
"sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793",
"sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f",
"sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a",
"sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f",
"sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30",
"sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f",
"sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3",
"sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"
"sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc",
"sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9",
"sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792",
"sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2",
"sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022",
"sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8",
"sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96",
"sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2",
"sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995",
"sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1",
"sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849",
"sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c",
"sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe",
"sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3",
"sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90",
"sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f",
"sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1",
"sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf",
"sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa",
"sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc",
"sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939",
"sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e",
"sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0",
"sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9",
"sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168",
"sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33",
"sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f",
"sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948"
],
"version": "==1.14.0"
"version": "==1.14.1"
},
"chardet": {
"hashes": [
@@ -115,10 +122,11 @@
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"version": "==7.0"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"coloredlogs": {
"hashes": [
@@ -130,79 +138,82 @@
},
"coverage": {
"hashes": [
"sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
"sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
"sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
"sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
"sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
"sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
"sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
"sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
"sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
"sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
"sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
"sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
"sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
"sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
"sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
"sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
"sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
"sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
"sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
"sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
"sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
"sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
"sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
"sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
"sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
"sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
"sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
"sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
"sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
"sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
"sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
"sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb",
"sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3",
"sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716",
"sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034",
"sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3",
"sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8",
"sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0",
"sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f",
"sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4",
"sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962",
"sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d",
"sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b",
"sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4",
"sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3",
"sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258",
"sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59",
"sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01",
"sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd",
"sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b",
"sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d",
"sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89",
"sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd",
"sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b",
"sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d",
"sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46",
"sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546",
"sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082",
"sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b",
"sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4",
"sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8",
"sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811",
"sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd",
"sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651",
"sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"
],
"index": "pypi",
"version": "==5.0.3"
"version": "==5.2.1"
},
"cryptography": {
"hashes": [
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
"sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b",
"sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd",
"sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a",
"sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07",
"sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71",
"sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756",
"sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559",
"sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f",
"sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261",
"sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053",
"sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2",
"sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f",
"sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b",
"sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77",
"sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83",
"sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f",
"sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67",
"sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c",
"sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6"
],
"version": "==2.8"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.0"
},
"decorator": {
"hashes": [
"sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce",
"sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d"
"sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
"sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
],
"version": "==4.4.1"
"version": "==4.4.2"
},
"defusedxml": {
"hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.6.0"
},
"dnspython": {
@@ -210,22 +221,24 @@
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"ecdsa": {
"hashes": [
"sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061",
"sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277"
"sha256:64c613005f13efec6541bb0a33290d0d03c27abab5f15fbab20fb0ee162bdd8e",
"sha256:e108a5fe92c67639abae3260e43561af914e7fd0d27bae6d2ec1312ae7934dfe"
],
"version": "==0.15"
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.14.1"
},
"eventlet": {
"hashes": [
"sha256:658b1cd80937adc1a4860de2841e0528f64e2ca672885c4e00fc0e2217bde6b1",
"sha256:6c9c625af48424c4680d89314dbe45a76cc990cf002489f9469ff214b044ffc1"
"sha256:4f4a43366b4cbd4a3f2f231816e5c3dae8ab316df9b7da11f0525e2800559f33",
"sha256:faa384fdd8e5797ab1f16979576fc93ce89a7e1e5622d345caec1bf767a274fd"
],
"index": "pypi",
"version": "==0.25.1"
"version": "==0.26.1"
},
"ffmpeg-python": {
"hashes": [
@@ -237,11 +250,11 @@
},
"flask": {
"hashes": [
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
],
"index": "pypi",
"version": "==1.1.1"
"version": "==1.1.2"
},
"flask-cors": {
"hashes": [
@@ -253,11 +266,11 @@
},
"flask-httpauth": {
"hashes": [
"sha256:0149953720489407e51ec24bc2f86273597b7973d71cd51f9443bd0e2a89bd72",
"sha256:6ef8b761332e780f9ff74d5f9056c2616f52babc1998b01d9f361a1e439e61b9"
"sha256:29e0288869a213c7387f0323b6bf2c7191584fb1da8aa024d9af118e5cd70de7",
"sha256:9e028e4375039a49031eb9ecc40be4761f0540476040f6eff329a31dabd4d000"
],
"index": "pypi",
"version": "==3.3.0"
"version": "==4.1.0"
},
"flask-jwt-extended": {
"hashes": [
@@ -276,26 +289,26 @@
},
"flask-migrate": {
"hashes": [
"sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1",
"sha256:a96ff1875a49a40bd3e8ac04fce73fdb0870b9211e6168608cbafa4eb839d502"
"sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732",
"sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"
],
"index": "pypi",
"version": "==2.5.2"
"version": "==2.5.3"
},
"flask-pyoidc": {
"hashes": [
"sha256:215f91ec5f08d7a40b5fc10f88bec06bf39840f9242bb18e0ead3a249c1329ee"
"sha256:6e8381fbb64ca62a37522e69c67e04f9074c54a7b81dcb7431fd290d79c4111b"
],
"index": "pypi",
"version": "==3.2.0"
"version": "==3.5.0"
},
"flask-restx": {
"hashes": [
"sha256:641759fe7cba1cb073d15b4258b1b15840af8cffe6edbd0da3e6b61eae0a67a6",
"sha256:fe0845112014201d618c1b0f3e41fa5f818a17c1d9e3fb6600d4e2c2bbc76a42"
"sha256:a1653da19ca0b5e5c2ea59bd5f4639a7749e6a9b882f459de1814ed37872253b",
"sha256:ca87a1808333f4ec5a50a5740b44e6cd3879a4b940d559df3996877ec4a2f2a5"
],
"index": "pypi",
"version": "==0.1.1"
"version": "==0.2.0"
},
"flask-script": {
"hashes": [
@@ -306,109 +319,111 @@
},
"flask-socketio": {
"hashes": [
"sha256:2172dff1e42415ba480cee02c30c2fc833671ff326f1598ee3d69aa02cf768ec",
"sha256:7ff5b2f5edde23e875a8b0abf868584e5706e11741557449bc5147df2cd78268"
"sha256:3668675bf7763c5b5f56689d439f07356e89c0a52e0c9e9cd3cc08563c07b252",
"sha256:36c1d5765010d1f4e4f05b4cc9c20c289d9dc70698c88d1addd0afcfedc5b062"
],
"index": "pypi",
"version": "==4.2.1"
"version": "==4.3.1"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d"
"sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e",
"sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"
],
"index": "pypi",
"version": "==2.4.1"
"version": "==2.4.4"
},
"flask-testing": {
"hashes": [
"sha256:dc076623d7d850653a018cb64f500948334c8aeb6b10a5a842bf1bcfb98122bc"
"sha256:d849bf53eb1ceb09dff6611888588cb60f20238058fb1ebcd917d69febc373e6"
],
"index": "pypi",
"version": "==0.7.1"
"version": "==0.8.0"
},
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"greenlet": {
"hashes": [
"sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0",
"sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28",
"sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8",
"sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304",
"sha256:51155342eb4d6058a0ffcd98a798fe6ba21195517da97e15fca3db12ab201e6e",
"sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0",
"sha256:7457d685158522df483196b16ec648b28f8e847861adb01a55d41134e7734122",
"sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214",
"sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043",
"sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6",
"sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625",
"sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc",
"sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638",
"sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163",
"sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4",
"sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490",
"sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248",
"sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939",
"sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87",
"sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720",
"sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656",
"sha256:e538b8dae561080b542b0f5af64d47ef859f22517f7eca617bb314e0e03fd7ef"
"sha256:1000038ba0ea9032948e2156a9c15f5686f36945e8f9906e6b8db49f358e7b52",
"sha256:133ba06bad4e5f2f8bf6a0ac434e0fd686df749a86b3478903b92ec3a9c0c90b",
"sha256:1429dc183b36ec972055e13250d96e174491559433eb3061691b446899b87384",
"sha256:1b805231bfb7b2900a16638c3c8b45c694334c811f84463e52451e00c9412691",
"sha256:3a35e33902b2e6079949feed7a2dafa5ac6f019da97bd255842bb22de3c11bf5",
"sha256:5ea034d040e6ab1d2ae04ab05a3f37dbd719c4dee3804b13903d4cc794b1336e",
"sha256:682328aa576ec393c1872615bcb877cf32d800d4a2f150e1a5dc7e56644010b1",
"sha256:6e06eac722676797e8fce4adb8ad3dc57a1bb3adfb0dd3fdf8306c055a38456c",
"sha256:7eed31f4efc8356e200568ba05ad645525f1fbd8674f1e5be61a493e715e3873",
"sha256:80cb0380838bf4e48da6adedb0c7cd060c187bb4a75f67a5aa9ec33689b84872",
"sha256:b0b2a984bbfc543d144d88caad6cc7ff4a71be77102014bd617bd88cfb038727",
"sha256:c196a5394c56352e21cb7224739c6dd0075b69dd56f758505951d1d8d68cf8a9",
"sha256:d83c1d38658b0f81c282b41238092ed89d8f93c6e342224ab73fb39e16848721",
"sha256:df7de669cbf21de4b04a3ffc9920bc8426cab4c61365fa84d79bf97401a8bef7",
"sha256:e5db19d4a7d41bbeb3dd89b49fc1bc7e6e515b51bbf32589c618655a0ebe0bf0",
"sha256:e695ac8c3efe124d998230b219eb51afb6ef10524a50b3c45109c4b77a8a3a92",
"sha256:eac2a3f659d5f41d6bbfb6a97733bc7800ea5e906dc873732e00cebb98cec9e4"
],
"version": "==0.4.15"
"version": "==0.4.16"
},
"html5lib": {
"hashes": [
"sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3",
"sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"
"sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d",
"sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"
],
"index": "pypi",
"version": "==1.0.1"
"version": "==1.1"
},
"humanfriendly": {
"hashes": [
"sha256:5e5c2b82fb58dcea413b48ab2a7381baa5e246d47fe94241d7d83724c11c0565",
"sha256:a9a41074c24dc5d6486e8784dc8f057fec8b963217e941c25fb7c7c383a4a1c1"
"sha256:bf52ec91244819c780341a3438d5d7b09f431d3f113a475147ac9b7b167a3d12",
"sha256:e78960b31198511f45fd455534ae7645a6207d33e512d2e842c766d15d9c8080"
],
"version": "==7.1.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==8.2"
},
"ics": {
"hashes": [
"sha256:12cf34aed0dafa1bf99d79ca58e99949d6721511b856386e118015fe5f5d6e3a"
"sha256:3b606205b9582ad27dff77f9b227a30d02fdac532731927fe39df1f1ddf8673f",
"sha256:bf5fbdef6e1e073afdadf1b996f0271186dd114a148e38e795919a1ae644d6ac"
],
"index": "pypi",
"version": "==0.6"
"version": "==0.7"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"version": "==2.9"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"importlib-resources": {
"hashes": [
"sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b",
"sha256:d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"
"sha256:19f745a6eca188b490b1428c8d1d4a0d2368759f32370ea8fb89cad2ab1106c3",
"sha256:d028f66b66c0d5732dae86ba4276999855e162a749c92620a38c1d779ed138a7"
],
"version": "==1.0.2"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.0.0"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"version": "==2.11.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2"
},
"jsonschema": {
"hashes": [
@@ -419,9 +434,11 @@
},
"mako": {
"hashes": [
"sha256:2984a6733e1d472796ceef37ad48c26f4a984bb18119bb2dbc37a44d8f6e75a4"
"sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
"sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
],
"version": "==1.1.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.3"
},
"markupsafe": {
"hashes": [
@@ -459,6 +476,7 @@
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"monotonic": {
@@ -473,6 +491,7 @@
"sha256:091b20c0a4866e5afeef8fc21bfdffd65382763f09d782e14f8ce9081326e1ed",
"sha256:865da7ade1291c2f39dd196c34e5641a782b29871c3a48289e317d62fa49ef20"
],
"markers": "python_version ~= '3.5'",
"version": "==1.1.2"
},
"passlib": {
@@ -485,94 +504,112 @@
},
"pbr": {
"hashes": [
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
"sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"
"sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
"sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
],
"version": "==5.4.4"
"version": "==5.4.5"
},
"pillow": {
"hashes": [
"sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
"sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
"sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
"sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
"sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
"sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
"sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
"sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
"sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
"sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
"sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
"sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
"sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
"sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
"sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
"sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
"sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
"sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
"sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
"sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
"sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
"sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
"sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f",
"sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8",
"sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad",
"sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f",
"sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae",
"sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d",
"sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5",
"sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b",
"sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8",
"sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233",
"sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6",
"sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727",
"sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f",
"sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38",
"sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4",
"sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626",
"sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d",
"sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6",
"sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63",
"sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f",
"sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41",
"sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1",
"sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d",
"sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9",
"sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a",
"sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"
],
"index": "pypi",
"version": "==7.0.0"
"version": "==7.2.0"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"version": "==2.19"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pycryptodomex": {
"hashes": [
"sha256:1537d2d15b604b303aef56e7f440895a1c81adbee786b91f1f06eddc34da5314",
"sha256:1d20ab8369b7558168fc014a0745c678613f9f486dae468cca2d68145196b8a4",
"sha256:1ecc9db7409db67765eb008e558879d298406642d33ade43a6488224d23e8081",
"sha256:37033976f72af829fe15f7fe5fe1dbed308cc43a98d9dd9d2a0a76de8ca5ee78",
"sha256:3c3dd9d4c9c1e279d3945ae422895c901f98987333acc132dc094faf52afec35",
"sha256:3c9b3fba037ea52c626060c5a87ee6de7e86c99e8a7c6ee07302539985d2bd64",
"sha256:45ee555fc5e28c119a46d44ce373f5237e54a35c61b750fb3a94446b09855dbc",
"sha256:4c93038ac011b36512cb0bf2ee3e2aec774e8bc81021d015917c89fe02bb0ee5",
"sha256:50163324834edd0c9ce3e4512ded3e221c969086e10fdd5d3fdcaadac5e24a78",
"sha256:59b0ea9cda5490f924771456912a225d8d9e678891f9f986661af718534719b2",
"sha256:5cf306a17cccc327a33cdc3845629fa13f4573a4ec620ed607c79cf6785f2e27",
"sha256:5fff8da399af16a1855f58771223acbbdac720b9969cd03fc5013d2e9a7bd9a4",
"sha256:68650ce5b9f7152b8283302a4617269f821695a612692640dd247bd12ab21c0b",
"sha256:6b3a9a562688996f760b5077714c3ab8b62ca56061b6e9ab7906841e43e19f91",
"sha256:7e938ed51a59e29431ea86fab60423ada2757728db0f78952329fa02a789bd31",
"sha256:87aa70daad6f039e814790a06422a3189311198b674b62f13933a2bdcb6b1bcc",
"sha256:99be3a1df2b2b9f731ebe1c264a2c07c465e71cee68e35e1640b645b5213a755",
"sha256:a3f2908666e6f74b8c4893f86dd02e16170f50e4a78ae7f3468b6208d54bc205",
"sha256:ae3d44a639fd11dbdeca47e35e94febb1ee8bc15daf26673331add37146e0b85",
"sha256:afb4c2fa3c6f492fd9a8b38d76e13f32d429b8e5e1e00238309391b5591cde0d",
"sha256:b1515ce3a8a2c3fa537d137c5ca5f8b7a902044d04e07d7c3aa26c3e026120fb",
"sha256:bf391b377413a197000b43ef2b74359974d8927d329a897c9f5ba7b63dca7b9c",
"sha256:c436919117c23355740c669f89720673578b9aa4569bbfe105f6c10101fc1966",
"sha256:d2c3c280975638e2a2c2fd9cb36ab111980219757fa163a2755594b9448e4138",
"sha256:e585d530764c459cbd5d460aed0288807bb881f376ca9a20e653645217895961",
"sha256:e76e6638ead4a7d93262a24218f0ff3ff74de6b6c823b7e19dccb31b6a481978",
"sha256:ebfc2f885cafda076c31ae30fa0dd81e7e919ec34059a88d3018ed66e83fcce3",
"sha256:f5797a39933a3d41526da60856735e6684b2b71a8ca99d5f79555ca121be2f4b",
"sha256:f7e5fc5e124200b19a14be33fb0099e956e6ebb5e25d287b0829ef0a78ed76c7",
"sha256:fb350e31e55211fec8ddc89fc0256f3b9bc3b44b68a8bde1cf44b3b4e80c0e42"
"sha256:06f5a458624c9b0e04c0086c7f84bcc578567dab0ddc816e0476b3057b18339f",
"sha256:1714675fb4ac29a26ced38ca22eb8ffd923ac851b7a6140563863194d7158422",
"sha256:17272d06e4b2f6455ee2cbe93e8eb50d9450a5dc6223d06862ee1ea5d1235861",
"sha256:2199708ebeed4b82eb45b10e1754292677f5a0df7d627ee91ea01290b9bab7e6",
"sha256:2275a663c9e744ee4eace816ef2d446b3060554c5773a92fbc79b05bf47debda",
"sha256:2710fc8d83b3352b370db932b3710033b9d630b970ff5aaa3e7458b5336e3b32",
"sha256:35b9c9177a9fe7288b19dd41554c9c8ca1063deb426dd5a02e7e2a7416b6bd11",
"sha256:3caa32cf807422adf33c10c88c22e9e2e08b9d9d042f12e1e25fe23113dd618f",
"sha256:48cc2cfc251f04a6142badeb666d1ff49ca6fdfc303fd72579f62b768aaa52b9",
"sha256:4ae6379350a09339109e9b6f419bb2c3f03d3e441f4b0f5b8ca699d47cc9ff7e",
"sha256:4e0b27697fa1621c6d3d3b4edeec723c2e841285de6a8d378c1962da77b349be",
"sha256:58e19560814dabf5d788b95a13f6b98279cf41a49b1e49ee6cf6c79a57adb4c9",
"sha256:8044eae59301dd392fbb4a7c5d64e1aea8ef0be2540549807ecbe703d6233d68",
"sha256:89be1bf55e50116fe7e493a7c0c483099770dd7f81b87ac8d04a43b1a203e259",
"sha256:8fcdda24dddf47f716400d54fc7f75cadaaba1dd47cc127e59d752c9c0fc3c48",
"sha256:914fbb18e29c54585e6aa39d300385f90d0fa3b3cc02ed829b08f95c1acf60c2",
"sha256:93a75d1acd54efed314b82c952b39eac96ce98d241ad7431547442e5c56138aa",
"sha256:9fd758e5e2fe02d57860b85da34a1a1e7037155c4eadc2326fc7af02f9cae214",
"sha256:a2bc4e1a2e6ca3a18b2e0be6131a23af76fecb37990c159df6edc7da6df913e3",
"sha256:a2ee8ba99d33e1a434fcd27d7d0aa7964163efeee0730fe2efc9d60edae1fc71",
"sha256:b2d756620078570d3f940c84bc94dd30aa362b795cce8b2723300a8800b87f1c",
"sha256:c0d085c8187a1e4d3402f626c9e438b5861151ab132d8761d9c5ce6491a87761",
"sha256:c990f2c58f7c67688e9e86e6557ed05952669ff6f1343e77b459007d85f7df00",
"sha256:ccbbec59bf4b74226170c54476da5780c9176bae084878fc94d9a2c841218e34",
"sha256:dc2bed32c7b138f1331794e454a953360c8cedf3ee62ae31f063822da6007489",
"sha256:e070a1f91202ed34c396be5ea842b886f6fa2b90d2db437dc9fb35a26c80c060",
"sha256:e42860fbe1292668b682f6dabd225fbe2a7a4fa1632f0c39881c019e93dea594",
"sha256:e4e1c486bf226822c8dceac81d0ec59c0a2399dbd1b9e04f03c3efa3605db677",
"sha256:ea4d4b58f9bc34e224ef4b4604a6be03d72ef1f8c486391f970205f6733dbc46",
"sha256:f60b3484ce4be04f5da3777c51c5140d3fe21cdd6674f2b6568f41c8130bcdeb"
],
"version": "==3.9.7"
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.9.8"
},
"pydub": {
"hashes": [
"sha256:c362fa02da1eebd1d08bd47aa9b0102582dff7ca2269dbe9e043d228a0c1ea93",
"sha256:d29901a486fb421c5d7b0f3d5d3a60527179204d8ffb20e74e1ae81c17e81b46"
"sha256:25fdfbbfd4c69363006a27c7bd2346c4b886a0dd3da264c14d858b71a9593284",
"sha256:630c68bfff9bb27cbc5e1f02923f717c3bc5f4d73fd685fda08b6ce90f76dc69"
],
"index": "pypi",
"version": "==0.23.1"
"version": "==0.24.1"
},
"pyjwkest": {
"hashes": [
@@ -588,87 +625,85 @@
"index": "pypi",
"version": "==1.7.1"
},
"pyreadline": {
"hashes": [
"sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"
],
"markers": "sys_platform == 'win32'",
"version": "==2.1"
},
"pyrsistent": {
"hashes": [
"sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280"
"sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"
],
"version": "==0.15.7"
"version": "==0.16.0"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"python-editor": {
"hashes": [
"sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d",
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b",
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8",
"sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77",
"sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"
],
"version": "==1.0.4"
},
"python-engineio": {
"hashes": [
"sha256:47ae4a9b3b4f2e8a68929f37a518338838e119f24c9a9121af92c49f8bea55c3",
"sha256:c3a3822deb51fdf9c7fe4d78abf807c73b83ea538036a50862d3024450746253"
"sha256:133bdb5fb89f43a53f8612fb1ddbb3a453318713dea18a9ecf5346ed0c0f793c",
"sha256:41353c2539493e9e30e0e75e53f9cbb670f09a5ebcf82fe738081a9ba28fe55c"
],
"version": "==3.11.2"
"version": "==3.13.1"
},
"python-jose": {
"hashes": [
"sha256:1ac4caf4bfebd5a70cf5bd82702ed850db69b0b6e1d0ae7368e5f99ac01c9571",
"sha256:8484b7fdb6962e9d242cce7680469ecf92bda95d10bbcbbeb560cacdff3abfce"
"sha256:4e4192402e100b5fb09de5a8ea6bcc39c36ad4526341c123d401e2561720335b",
"sha256:67d7dfff599df676b04a996520d9be90d6cdb7e6dd10b4c7cacc0c3e2e92f2be"
],
"index": "pypi",
"version": "==3.1.0"
"version": "==3.2.0"
},
"python-socketio": {
"extras": [
"client"
],
"hashes": [
"sha256:48cba5b827ac665dbf923a4f5ec590812aed5299a831fc43576a9af346272534",
"sha256:af6c23c35497960f82106e36688123ecb52ad5a77d0ca27954ff3811c4d9d562"
"sha256:358d8fbbc029c4538ea25bcaa283e47f375be0017fcba829de8a3a731c9df25a",
"sha256:d437f797c44b6efba2f201867cf02b8c96b97dff26d4e4281ac08b45817cd522"
],
"index": "pypi",
"version": "==4.4.0"
"version": "==4.6.0"
},
"pythonping": {
"hashes": [
"sha256:05269d459d2290ff57665aa3b3c9ed1b64bb96106d22712e0054b52d51c6bb13"
"sha256:0338c6cbb03b7318b38d33c42526faf7f71dd25d9a627b3cf29e741e26a1f7ac",
"sha256:94f7226c0d98cc0bd739c5f00f65aeffa1e0e7bbbf01eff529c912a25dfdf7fc"
],
"index": "pypi",
"version": "==1.0.8"
"version": "==1.0.11"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2019.3"
"version": "==2020.1"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"version": "==2.23.0"
"version": "==2.24.0"
},
"rsa": {
"hashes": [
"sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66",
"sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487"
"sha256:109ea5a66744dd859bf16fe904b8d8b627adafb9408753161e766a92e7d681fa",
"sha256:6166864e23d6b5195a5cfed6cd9fed0fe774e226d8f854fcb23b7bbef0350233"
],
"version": "==4.0"
"markers": "python_version >= '3.5' and python_version < '4'",
"version": "==4.6"
},
"scapy": {
"hashes": [
@@ -692,10 +727,11 @@
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.14.0"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"socketio-client": {
"hashes": [
@@ -706,17 +742,45 @@
},
"soupsieve": {
"hashes": [
"sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae",
"sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69"
"sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
"sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
],
"version": "==2.0"
"markers": "python_version >= '3.5'",
"version": "==2.0.1"
},
"sqlalchemy": {
"hashes": [
"sha256:64a7b71846db6423807e96820993fa12a03b89127d278290ca25c0b11ed7b4fb"
"sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e",
"sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772",
"sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7",
"sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf",
"sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98",
"sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864",
"sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9",
"sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1",
"sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd",
"sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4",
"sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1",
"sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c",
"sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8",
"sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e",
"sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce",
"sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1",
"sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5",
"sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe",
"sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413",
"sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3",
"sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284",
"sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1",
"sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7",
"sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299",
"sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33",
"sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d",
"sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274",
"sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd"
],
"index": "pypi",
"version": "==1.3.13"
"version": "==1.3.18"
},
"sqlalchemy-migrate": {
"hashes": [
@@ -728,10 +792,11 @@
},
"sqlparse": {
"hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.0"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.3.1"
},
"ssh2-python": {
"hashes": [
@@ -771,6 +836,7 @@
"sha256:0adbf7189a8c4f9a882b442f7b8ed6c6ab3baae37057db0e96b6888daacffad0",
"sha256:3a043490e577632a05374b5033646bbc26cbb17386df81735a569ecbd45d934b"
],
"markers": "python_version >= '3.8'",
"version": "==5.5.0"
},
"tempita": {
@@ -781,18 +847,18 @@
},
"typing-extensions": {
"hashes": [
"sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2",
"sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d",
"sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"
"sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5",
"sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae",
"sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"
],
"version": "==3.7.4.1"
"version": "==3.7.4.2"
},
"tzlocal": {
"hashes": [
"sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048",
"sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"
"sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44",
"sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"
],
"version": "==2.0.0"
"version": "==2.1"
},
"update": {
"hashes": [
@@ -803,10 +869,11 @@
},
"urllib3": {
"hashes": [
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
],
"version": "==1.25.8"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.25.10"
},
"webencodings": {
"hashes": [
@@ -825,10 +892,11 @@
},
"werkzeug": {
"hashes": [
"sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2",
"sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
],
"version": "==0.16.1"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.0.1"
}
},
"develop": {}

View File

@@ -3,6 +3,8 @@
Backend base module
"""
import logging
import os
from functools import wraps
from io import StringIO
from logging.config import dictConfig
from logging.handlers import MemoryHandler
@@ -13,7 +15,7 @@ import jwt
import requests
from flask import Flask, jsonify, abort
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
from flask_jwt_extended import JWTManager, decode_token
from flask_jwt_extended import JWTManager, decode_token, get_jwt_identity
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
@@ -120,7 +122,15 @@ class LrcException(Exception):
app = Flask(__name__)
app.config.from_object('backend.config.Config')
if os.environ.get('FLASK_ENV', None) in ["development", "dev"]:
print("#using backend.config.DevelopmentConfig")
app.config.from_object('backend.config.DevelopmentConfig')
elif os.environ.get('FLASK_ENV', None) in ["test", "testing"]:
print("#using backend.config.TestingConfig")
app.config.from_object('backend.config.TestingConfig')
else:
app.config.from_object('backend.config.Config')
print("#using backend.config.Config")
db = SQLAlchemy(app)
login_manager = LoginManager()
@@ -132,12 +142,6 @@ jwt_extended = JWTManager(app)
jwt_auth = HTTPTokenAuth('Bearer')
@jwt_extended.invalid_token_loader
def unauthorized_jwt(token):
main_logger.info("Unauthorized access; invalid token provided: {}".format(token))
abort(401)
@jwt_auth.verify_token
def verify_token(token):
"""This function (and HTTPTokenAuth('Bearer')) has been defined to be used together with MultiAuth. For API calls
@@ -177,6 +181,22 @@ app.register_blueprint(fe_bp)
CORS(app)
CORS(api_bp)
CORS(auth_api_bp)
# Fix jwt_extended by duck typing error handlers
jwt_extended._set_error_handler_callbacks(api_v1)
logging.getLogger('flask_cors').level = logging.DEBUG
# Fix jwt_extended by 'duck typing' error handlers
# jwt_extended._set_error_handler_callbacks(api_v1) # removed for the moment, might raise new (old) problems
@jwt_extended.invalid_token_loader
def unauthorized_jwt(token):
main_logger.info("Unauthorized access; invalid token provided: {}".format(token))
abort(401)
@jwt_extended.expired_token_loader
def unauthorized_jwt(token):
main_logger.info("Unauthorized access; expired token provided: {}".format(token))
abort(401)

View File

@@ -5,55 +5,98 @@
import logging
import ssl
import sys
import threading
from jinja2.exceptions import TemplateNotFound
from backend import app, db
from backend.cron import get_default_scheduler, add_default_jobs
from backend import app, db, main_logger
from backend.cron import get_default_scheduler, add_default_jobs, async_permanent_cron_recorder_checker
from backend.models import *
from backend.models import room_model, recorder_model, RecorderCommand, Recorder
from backend.recorder_adapters import get_defined_recorder_adapters
from backend.tools.model_updater import update_recorder_models_database, create_default_recorders
from backend.tools.model_updater import update_recorder_models_database, create_default_recorders, add_test_recorder
from backend.websocket.base import WebSocketBase
def main():
def _start_initial_recorder_state_update(run_in_thread=True):
if run_in_thread:
thread = threading.Thread(target=async_permanent_cron_recorder_checker.check_object_state, args=())
thread.start()
else:
async_permanent_cron_recorder_checker.check_object_state() # initial check of all recorders
def _create_and_start_default_scheduler():
print("Starting Scheduler")
scheduler = get_default_scheduler()
add_default_jobs(scheduler)
scheduler.start()
return scheduler
def run():
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
print("starting ...")
#db.drop_all()
#db.create_all()
# db.drop_all()
# db.create_all()
#Recorder()
# Recorder()
room_model.pre_fill_table()
update_recorder_models_database(drop=False)
create_default_recorders()
print(app.config.get("SERVER_NAME", None))
server_name = app.config.get("SERVER_NAME", None)
if server_name is not None and "ubkaps154.ubka.uni-karlsruhe.de" in server_name:
try:
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain('cert.pem', 'key.pem')
app.run(debug=True, ssl_context=context, threaded=True)
except FileNotFoundError:
app.run(debug=True, threaded=True)
add_test_recorder()
try:
db.create_all()
except Exception as e:
logging.critical(e)
print("Starting Scheduler")
scheduler = get_default_scheduler()
add_default_jobs(scheduler)
scheduler.start()
scheduler = _create_and_start_default_scheduler()
# _start_initial_recorder_state_update(run_in_thread=False)
print("Server Name: {}".format(app.config.get("SERVER_NAME", None)))
wsb = WebSocketBase()
print("running websocket...(replaces normal app.run()")
wsb.start_websocket(debug=True)
if app.config.get("USE_SSL", False):
try:
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(app.config.get("CERT", 'cert.pem'), app.config.get("KEY", 'key.pem'))
print("using ssl context!")
#app.run(debug=True, ssl_context=context, threaded=True,
# host=app.config.get("HOST", "0.0.0.0"),
# port=app.config.get("PORT", 5443)
# )
print("running websocket...(replaces normal app.run()")
wsb.start_websocket(debug=app.config.get("DEBUG", False),
host=app.config.get("HOST", "0.0.0.0"),
port=app.config.get("PORT", 5443),
ssl_context=context
)
except FileNotFoundError:
print("Could not find cert/key.pem!")
#app.run(debug=True, threaded=True,
# host=app.config.get("HOST", None),
# port=app.config.get("PORT", 5443)
# )
print("running websocket...(replaces normal app.run()")
wsb.start_websocket(debug=app.config.get("DEBUG", False),
host=app.config.get("HOST", "0.0.0.0"),
port=app.config.get("PORT", 5443)
)
else:
print("running websocket...(replaces normal app.run()")
wsb.start_websocket(debug=app.config.get("DEBUG", False),
host=app.config.get("HOST", "0.0.0.0"),
port=app.config.get("PORT", 5443)
)
# print("running web app...")
#app.run(debug=True, host="0.0.0.0", threaded=True)
# app.run(debug=True, host="0.0.0.0", threaded=True)
wsb.send_test_msg()
print("running!")
while True:
user_in = input("Type >exit< to quit.")
@@ -65,4 +108,4 @@ def main():
if __name__ == '__main__':
main()
run()

View File

@@ -2,6 +2,7 @@
from flask import Blueprint, abort
from flask_restx import Api, Namespace
from werkzeug.exceptions import InternalServerError
api_authorizations = {
'apikey': {
@@ -73,6 +74,19 @@ from .virtual_command_api import *
# from .group_api import *
"""
@api_v1.errorhandler(InternalServerError)
def handle_500(e):
original = getattr(e, "original_exception", None)
if original is None:
# direct 500 error, such as abort(500)
api_v1.abort(500)
# wrapped unhandled error
#return render_template("500_unhandled.html", e=original), 500
api_v1.abort(500)
"""
@api_bp.route('/<path:path>')
def catch_all_api(path):

View File

@@ -7,6 +7,7 @@ Login through API does not start a new session, but instead returns JWT.
"""
import base64
import json
import logging
from pprint import pprint
import flask
@@ -21,14 +22,19 @@ from random import randint
from flask_login import logout_user, login_user
from typing import Iterable
from flask_restx import Resource, fields
from flask_restx import Resource, fields, abort, inputs
from sqlalchemy.exc import IntegrityError
from werkzeug.routing import BuildError
from backend import db, app, jwt_extended
from backend.api import auth_api_bp, auth_api_providers_ns, auth_api_register_ns
from backend.api.models import user_model
from backend.auth import AUTH_PROVIDERS, oidc_auth
from backend.auth.oidc_config import PROVIDER_NAME
from backend.models.user_model import User, Group, BlacklistToken
logger = logging.getLogger("lrc.api.auth")
@auth_api_bp.route('/providers', methods=('GET',))
def get_auth_providers():
@@ -49,15 +55,6 @@ class AuthProviders(Resource):
return get_auth_providers()
@auth_api_bp.route('/register', methods=('POST',))
def register():
data = request.get_json()
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
@auth_api_register_ns.route('/')
@auth_api_register_ns.expect(auth_api_register_ns.model('RegisterModel', {
'nickname': fields.String(required=False, description='The user\'s nickname'),
@@ -65,12 +62,24 @@ def register():
'last_name': fields.String(required=False, description='The user\'s last name'),
'lang': fields.String(required=False, description='The user\'s preferred language'),
'timezone': fields.String(required=False, description='The user\'s preferred timezone'),
'email': fields.String(required=True, description='The user\'s e-mail address'),
'email': fields.String(required=True, type=inputs.email(), description='The user\'s e-mail address'),
'password': fields.String(required=False, description='The group\'s name')
}))
class AuthProviders(Resource):
def get(self):
return register()
class Registration(Resource):
@auth_api_register_ns.marshal_list_with(user_model)
def post(self):
print("in registration")
data = request.get_json()
try:
user = User(**data)
db.session.add(user)
db.session.commit()
pprint(user.to_dict())
return user, 201
except IntegrityError as e:
abort(400, message=str(e).split('\n')[0].split(')')[1].strip())
except AssertionError as e:
abort(400, message=str(e))
@auth_api_bp.route('/login', methods=('GET', 'POST',))
@@ -92,6 +101,7 @@ def login():
}
return jsonify(token), 200
# Endpoint for revoking the current users access token
@auth_api_bp.route('/logout', methods=['GET', 'DELETE'])
@jwt_required
@@ -104,6 +114,7 @@ def logout():
# Endpoint for revoking the current users refresh token
@auth_api_bp.route('/logout2', methods=['GET', 'DELETE'])
@auth_api_bp.route('/revokeRefreshToken', methods=['GET', 'DELETE'])
@jwt_refresh_token_required
def logout2():
jti = get_raw_jwt()['jti']
@@ -129,16 +140,19 @@ def create_or_retrieve_user_from_userinfo(userinfo):
try:
email = userinfo["email"]
except KeyError:
logger.error("email is missing in OIDC userinfo! Can't create user!")
return None
pprint(userinfo)
user_groups = check_and_create_groups(groups=userinfo.get("memberOf", []))
user = User.get_by_identifier(email)
if user is not None:
app.logger.info("user found -> update user")
logger.info("user found -> update user")
pprint(user.to_dict())
user.first_name = userinfo.get("given_name", "")
user.last_name = userinfo.get("family_name", "")
user.external_user_id = userinfo.get("eduperson_principal_name", None)
for g in user_groups:
user.groups.append(g)
db.session.commit()
@@ -146,9 +160,9 @@ def create_or_retrieve_user_from_userinfo(userinfo):
user = User(email=email, first_name=userinfo.get("given_name", ""),
last_name=userinfo.get("family_name", ""), external_user=True,
groups=user_groups)
groups=user_groups, external_user_id=userinfo.get("eduperson_principal_name", None))
app.logger.info("creating new user")
logger.info("creating new user")
db.session.add(user)
db.session.commit()
@@ -157,10 +171,12 @@ def create_or_retrieve_user_from_userinfo(userinfo):
@auth_api_bp.route('/oidc', methods=['GET'])
@auth_api_bp.route('/oidc/<redirect_url>', methods=['GET'])
@oidc_auth.oidc_auth()
@oidc_auth.oidc_auth(provider_name=PROVIDER_NAME)
def oidc(redirect_url=None):
logger.debug("oidc auth endpoint:")
user = create_or_retrieve_user_from_userinfo(flask.session['userinfo'])
if user is None:
logger.error(f"Could not authenticate: could not find or create user:\n{str(flask.session['userinfo'])}")
return "Could not authenticate: could not find or create user.", 401
if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False):
token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY'])
@@ -169,13 +185,13 @@ def oidc(redirect_url=None):
'access_token': create_access_token(identity=user, fresh=True),
'refresh_token': create_refresh_token(identity=user)
})
logger.info("Token: {}".format(token))
if redirect_url is None:
redirect_url = request.headers.get("Referer")
if redirect_url is None:
redirect_url = request.args.get('redirect_url')
if redirect_url is None:
redirect_url = "/"
app.logger.info("Token: {}".format(token))
response = make_response(redirect(redirect_url))
response.set_cookie('tokens', base64.b64encode(token.encode('utf-8')))
return response
@@ -189,8 +205,7 @@ def refresh():
as we do not actually verify a password in this endpoint."""
jwt_identity = get_jwt_identity()
user = User.get_by_identifier(jwt_identity)
app.logger.info("Refreshing token for " + str(user))
logger.info("Refreshing token for " + str(user))
new_token = create_access_token(identity=user, fresh=False)
ret = {'access_token': new_token}
return jsonify(ret), 200

View File

@@ -12,9 +12,15 @@ user_model = api_user.model('User', {
'nickname': fields.String(required=False, description='The user\'s nick name'),
'last_seen': fields.DateTime(required=False, description='Last time user logged in'),
'last_time_modified': fields.DateTime(required=False, description='Last time user was modified'),
'external_user': fields.Boolean(required=True, description='Indicates whether the user is external (OIDC) or not'),
'external_user_id': fields.String(required=False, description='External ID of a user (EPPN, etc.)'),
'role': fields.String(required=False, description='Role a user might have (in addition to group memberships)'),
'effective_permissions': fields.List(
fields.String(required=True), required=False, description="List of permissions (groups + (optional) role)."
fields.Nested(api_user.model('effective_permission',
{'id': fields.Integer(required=True),
'name': fields.String(required=True)
}),
required=False, description="List of permissions (groups + (optional) role).")
),
'groups': fields.List(
fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})),
@@ -45,7 +51,8 @@ recorder_model = api_recorder.model('Recorder', {
'additional_camera_connected': fields.Boolean(required=False,
description='Indicates whether an additional camera is connected'),
'ip': fields.String(required=False, description='The recorder\'s IP address'),
'mac': fields.String(required=False, description='The recorder\'s IP address'),
'ip6': fields.String(required=False, description='The recorder\'s IP v6 address'),
'mac': fields.String(required=False, description='The recorder\'s MAC address'),
'network_name': fields.String(required=False, description='The recorder\'s network name'),
'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'),
'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'),
@@ -103,5 +110,15 @@ recorder_model_model = api_recorder.model('Recorder Model', {
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
})
state_model = api_state.model('Recorder', {
state_model = api_state.model('Recorder State', {
'id': fields.String(required=False, description='The recorder model\'s identifier'),
'name': fields.String(description='The recorder model\'s name'),
'msg': fields.String(),
'state_ok': fields.Boolean(),
'time_stamp': fields.String(required=False),
'previous': fields.Nested(api_state.model('Previous Recorder State', {
'msg': fields.String(),
'state_ok': fields.Boolean(),
'time_stamp': fields.String()}, required=False, skip_none=True)
)
})

View File

@@ -2,19 +2,22 @@
"""
This module provides functions related to recorders through the API.
"""
import logging
from datetime import datetime
from pprint import pprint
from flask_jwt_extended import jwt_required
from flask_restx import fields, Resource, inputs
from backend import db, app
from backend import db, app, LrcException, Config
from backend.api import api_recorder
from backend.api.models import recorder_model, recorder_model_model, recorder_command_model
from backend.auth.utils import requires_permission_level
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room
import backend.recorder_adapters as r_a
logger = logging.getLogger("lrc.api.recorder")
# ==
@@ -23,6 +26,7 @@ import backend.recorder_adapters as r_a
@api_recorder.param('id', 'The recorder identifier')
class RecorderResource(Resource):
@jwt_required
@requires_permission_level(Config.Permissions.RECODER_SHOW)
@api_recorder.doc('get_recorder')
@api_recorder.marshal_with(recorder_model, skip_none=False)
def get(self, id):
@@ -33,6 +37,7 @@ class RecorderResource(Resource):
api_recorder.abort(404)
@jwt_required
@requires_permission_level(Config.Permissions.RECORDER_DELETE)
@api_recorder.doc('delete_todo')
@api_recorder.response(204, 'Todo deleted')
def delete(self, id):
@@ -63,6 +68,7 @@ class RecorderResource(Resource):
required=False, store_missing=False)
@jwt_required
@requires_permission_level(Config.Permissions.RECORDER_EDIT)
@api_recorder.doc('update_recorder')
@api_recorder.expect(recorder_model)
def put(self, id):
@@ -83,6 +89,7 @@ class RecorderResource(Resource):
@api_recorder.route('')
class RecorderList(Resource):
@jwt_required
@requires_permission_level(Config.Permissions.RECORDERS_LIST)
@api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model, skip_none=False)
def get(self):
@@ -93,6 +100,7 @@ class RecorderList(Resource):
return Recorder.get_all()
@jwt_required
@requires_permission_level(Config.Permissions.RECODER_NEW)
@api_recorder.doc('create_recorder')
@api_recorder.expect(recorder_model)
@api_recorder.marshal_with(recorder_model, skip_none=False, code=201)
@@ -116,10 +124,14 @@ class RecorderList(Resource):
else:
return "specified recorder model (id: {}) does not exist!".format(
api_recorder.payload["recorder_model_id"]), 404
recorder = Recorder(**api_recorder.payload)
db.session.add(recorder)
db.session.commit()
return recorder
try:
recorder = Recorder(**api_recorder.payload)
db.session.add(recorder)
db.session.commit()
return recorder
except LrcException as e:
logger.error(e)
return api_recorder.abort(400, message=str(e))
@api_recorder.route('/model/<int:id>')
@@ -155,6 +167,7 @@ class RecorderModelResource(Resource):
@api_recorder.route('/model')
class RecorderModelList(Resource):
@jwt_required
@requires_permission_level(Config.Permissions.RECODER_MODELS_LIST)
@api_recorder.doc('recorders')
@api_recorder.marshal_list_with(recorder_model_model)
def get(self):
@@ -166,6 +179,7 @@ class RecorderModelList(Resource):
@api_recorder.param('id', 'The recorder command identifier')
class RecorderCommandResource(Resource):
@jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMAND_SHOW)
@api_recorder.doc('get_recorder_command')
@api_recorder.marshal_with(recorder_command_model)
def get(self, id):
@@ -180,6 +194,7 @@ class RecorderCommandResource(Resource):
recorder_command_model_parser.add_argument('alternative_name', type=str, required=False)
@jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMAND_EDIT)
@api_recorder.doc('update_recorder_command')
@api_recorder.expect(recorder_command_model_parser)
@api_recorder.marshal_with(recorder_command_model)
@@ -195,6 +210,7 @@ class RecorderCommandResource(Resource):
@api_recorder.route('/command')
class RecorderCommandList(Resource):
@jwt_required
@requires_permission_level(Config.Permissions.RECORDER_COMMANDS_LIST)
@api_recorder.doc('recorder_commands')
@api_recorder.marshal_list_with(recorder_command_model)
def get(self):

View File

@@ -5,15 +5,20 @@ For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
"""
import logging
from flask_jwt_extended import jwt_required
from flask_restx import fields, Resource
from sqlalchemy import exc
from backend import db, app
from backend import db, app, LrcException
from backend.api import api_room
from backend.models.room_model import Room
from backend.models.recorder_model import Recorder
logger = logging.getLogger("lrc.api.room")
room_model = api_room.model('Room', {
'id': fields.String(required=False, description='The room\'s identifier'),
'created_at': fields.DateTime(required=False, description='Creation date of the room info'),
@@ -80,8 +85,8 @@ class RoomResource(Resource):
room.recorder = api_room.payload["recorder"]
else:
num_rows_matched = Room.query.filter_by(id=id).update(api_room.payload)
db.session.commit()
return "ok"
db.session.commit()
return "ok"
api_room.abort(404)
@@ -112,11 +117,15 @@ class RoomList(Resource):
else:
return "specified recorder (id: {}) does not exist!".format(api_room.payload["recorder_id"]), 404
del api_room.payload["recorder_id"]
room = Room(**api_room.payload)
db.session.add(room)
try:
db.session.commit()
return room
except exc.IntegrityError as e:
db.session.rollback()
return str(e.detail), 400
room = Room(**api_room.payload)
db.session.add(room)
try:
db.session.commit()
return room
except exc.IntegrityError as e:
db.session.rollback()
return str(e.detail), 400
except LrcException as e:
logger.error(e)
return api_room.abort(400, message=str(e))

View File

@@ -11,13 +11,12 @@ from flask_restx import fields, Resource, inputs
from backend import db, app
from backend.api import api_state
from backend.api.models import recorder_model, recorder_model_model, recorder_command_model, state_model
from backend.cron import async_cron_recorder_checker
from backend.cron import async_cron_recorder_checker, async_permanent_cron_recorder_checker
from backend.cron.cron_state_checker import StateChecker
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room
import backend.recorder_adapters as r_a
# ==
from backend.tools.recorder_state_checker import get_recorder_adapter, check_capture_agent_state, check_stream_sanity
@@ -35,33 +34,29 @@ class RecorderStateResource(Resource):
if recorder is None:
api_state.abort(404)
current_states_by_checker = async_cron_recorder_checker.get_current_state()
state = async_cron_recorder_checker.get_current_state_for_recorder_id(recorder.id)
# current_states_by_checker = async_cron_recorder_checker.get_current_state()
state = async_permanent_cron_recorder_checker.get_current_state_for_recorder_id(recorder.id)
if state is None:
state_checker = StateChecker([check_capture_agent_state], Recorder)
state_checker.add_object_to_state_check(recorder)
state_checker.check_object_state()
state = state_checker.get_current_state_for_recorder_id(recorder.id)
if not state.get('state_ok', False): # if state is not OK, return state -> no more checks!
return state
# do additional checks, such as: check for single color, sound check, etc.
stream_state = check_stream_sanity(recorder)
print(stream_state)
return stream_state
print("lalala")
print(state)
return state
@api_state.route('/recorder')
class RecorderStateList(Resource):
@jwt_required
@api_state.doc('get_recorders_states')
@api_state.marshal_list_with(state_model, skip_none=False)
@api_state.marshal_list_with(state_model, skip_none=True)
def get(self):
"""
Get state of all recorders
:return: state
"""
return Recorder.get_all()
rec_states = async_permanent_cron_recorder_checker.get_current_state()
print(rec_states)
res = [{**{'name': rec_state_name}, **rec_states[rec_state_name]} for rec_state_name in rec_states]
return res

View File

@@ -14,7 +14,8 @@ from flask_restx import Resource, fields, inputs, abort
from backend import db, app, jwt_auth
from backend.api import api_user
from backend.api.models import user_model, recorder_model, generic_id_parser
from backend.models import Recorder
from backend.auth.utils import requires_permission_level
from backend.models import Recorder, Config
from backend.models.user_model import User, Group
@@ -90,18 +91,20 @@ class UserList(Resource):
# @jwt_auth.login_required
@jwt_required
@requires_permission_level(Config.Permissions.USERS_LIST)
@api_user.doc('users')
@api_user.marshal_list_with(user_model)
def get(self):
"""
just a test!
:return: Hello: World
returns all users
:return: all users
"""
current_user = get_jwt_identity()
app.logger.info(current_user)
return User.get_all()
@jwt_required
@requires_permission_level(Config.Permissions.USER_CREATE)
@api_user.doc('create_group')
@api_user.expect(user_model)
@api_user.marshal_with(user_model, code=201)
@@ -117,6 +120,7 @@ class UserList(Resource):
@api_user.response(404, 'User not found')
class UserResource(Resource):
@jwt_auth.login_required
@requires_permission_level(Config.Permissions.USER_SHOW)
@api_user.doc('get_user')
@api_user.marshal_with(user_model)
def get(self, id):
@@ -126,4 +130,19 @@ class UserResource(Resource):
return user
api_user.abort(404)
@jwt_auth.login_required
@requires_permission_level(Config.Permissions.USER_DELETE)
@api_user.doc('delete_user')
def delete(self, id):
"""Fetch a user given its identifier"""
user = User.get_by_id(id)
if user is not None:
if str(user.role) == str(Config.Roles.ADMIN):
print("role deletion forbidden")
return api_user.abort(403, message="It is not allowed to delete role admin users!")
db.session.delete(user)
db.session.commit()
return "ok"
api_user.abort(404)
# api_user.add_resource(UserResource, '/')

View File

@@ -13,7 +13,8 @@ from flask_jwt_extended import jwt_required
from flask_restx import fields, Resource
from backend import db, app
from backend.api import api_virtual_command
from backend.api import api_virtual_command, recorder_command_model
from backend.models import VirtualCommand
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
from backend.models.room_model import Room
import backend.recorder_adapters as r_a
@@ -36,6 +37,10 @@ virtual_command_model = api_virtual_command.model('VirtualCommand', {
allow_null=True,
skip_none=False,
description='Parent virtual command.')),
'recorder_commands': fields.List(fields.Nested(recorder_command_model)),
'command_default_params': fields.List(fields.Raw()),
'room': fields.Nested(api_virtual_command.model('recorder_room',
{'id': fields.Integer(), 'name': fields.String(),
'number': fields.String(), 'alternate_name': fields.String()}),
@@ -57,9 +62,9 @@ class VirtualCommandResource(Resource):
@api_virtual_command.marshal_with(virtual_command_model, skip_none=False)
def get(self, id):
"""Fetch a recorder given its identifier"""
recorder = Recorder.query.get(id)
if recorder is not None:
return recorder
command = VirtualCommand.query.get(id)
if command is not None:
return command
api_virtual_command.abort(404)
@jwt_required
@@ -90,7 +95,7 @@ class VirtualCommandResource(Resource):
@api_virtual_command.route('')
class RecorderList(Resource):
class VirtualCommandList(Resource):
@jwt_required
@api_virtual_command.doc('recorders')
@api_virtual_command.marshal_list_with(virtual_command_model, skip_none=False)
@@ -99,7 +104,7 @@ class RecorderList(Resource):
List all recorders
:return: recorders
"""
return Recorder.get_all()
return VirtualCommand.get_all()
virtual_command_model_parser = api_virtual_command.parser()
virtual_command_model_parser.add_argument('notes', type=str, required=True)
@@ -109,26 +114,37 @@ class RecorderList(Resource):
@api_virtual_command.expect(virtual_command_model_parser)
@api_virtual_command.marshal_with(virtual_command_model, skip_none=False, code=201)
def post(self):
if "room_id" in api_virtual_command.payload:
if api_virtual_command.payload["room_id"] is None:
api_virtual_command.payload["room"] = None
pprint(api_virtual_command.payload)
room_id = api_virtual_command.payload.pop('recorder_id', None)
if room_id is None:
api_virtual_command.payload["room"] = None
else:
room = Room.query.get(room_id)
if room is not None:
api_virtual_command.payload["room"] = room
else:
room = Room.query.get(api_virtual_command.payload["room_id"])
if room is not None:
api_virtual_command.payload["room"] = room
else:
return "specified room (id: {}) does not exist!".format(api_virtual_command.payload["room_id"]), 404
if "recorder_model_id" in api_virtual_command.payload:
if api_virtual_command.payload["recorder_model_id"] is None:
api_virtual_command.payload["recorder_model"] = None
return "specified v-command (id: {}) does not exist!".format(api_virtual_command.payload["room_id"]), 404
recorder_model_id = api_virtual_command.payload.pop('recorder_model_id', None)
if recorder_model_id is None:
api_virtual_command.payload["recorder_model"] = None
else:
rec_model = RecorderModel.query.get(recorder_model_id)
if rec_model is not None:
api_virtual_command.payload["recorder_model"] = rec_model
else:
rec_model = RecorderModel.query.get(api_virtual_command.payload["recorder_model_id"])
if rec_model is not None:
api_virtual_command.payload["recorder_model"] = rec_model
else:
return "specified recorder model (id: {}) does not exist!".format(
api_virtual_command.payload["recorder_model_id"]), 404
recorder = Recorder(**api_virtual_command.payload)
db.session.add(recorder)
return "specified recorder model (id: {}) does not exist!".format(
api_virtual_command.payload["recorder_model_id"]), 404
recorder_id = api_virtual_command.payload.pop('recorder_id', None)
if recorder_id is None:
api_virtual_command.payload["recorder"] = None
else:
recorder = Recorder.query.get(recorder_id)
if recorder is not None:
api_virtual_command.payload["recorder"] = recorder
else:
return "specified v-command (id: {}) does not exist!".format(
api_virtual_command.payload["recorder_id"]), 404
virtual_command = VirtualCommand(**api_virtual_command.payload)
db.session.add(virtual_command)
db.session.commit()
return recorder
return virtual_command

BIN
backend/app.db_debug Normal file

Binary file not shown.

BIN
backend/app.db_old Normal file

Binary file not shown.

BIN
backend/app.db_test Normal file

Binary file not shown.

View File

@@ -15,11 +15,10 @@ from backend.models.user_model import User
from . import auth_bp
from .oidc_config import PROVIDER_NAME, OIDC_PROVIDERS
OIDCAuthentication.oidc_auth_orig = OIDCAuthentication.oidc_auth
OIDCAuthentication.oidc_logout_orig = OIDCAuthentication.oidc_logout
'''
def oidc_auth_default_provider(self):
"""monkey patch oidc_auth"""
return self.oidc_auth_orig(PROVIDER_NAME)
@@ -32,6 +31,7 @@ def oidc_logout_default_provider(self):
OIDCAuthentication.oidc_auth = oidc_auth_default_provider
OIDCAuthentication.oidc_logout = oidc_logout_default_provider
'''
oidc_auth = OIDCAuthentication(OIDC_PROVIDERS)
@@ -41,11 +41,13 @@ def create_or_retrieve_user_from_userinfo(userinfo):
try:
email = userinfo["email"]
except KeyError:
app.logger.error("email is missing in OIDC userinfo! Can't create user!")
return None
user = User.get_by_identifier(email)
if user is not None:
app.logger.info("user found")
app.logger.debug(f"user found: {email}")
user.last_seen = datetime.utcnow()
# TODO: update user!
db.session.commit()
@@ -54,27 +56,38 @@ def create_or_retrieve_user_from_userinfo(userinfo):
user = User(email=email, first_name=userinfo.get("given_name", ""),
last_name=userinfo.get("family_name", ""))
app.logger.info("creating new user")
app.logger.info(f"creating new user: {email}")
db.session.add(user)
db.session.commit()
return user
@auth_bp.route('/oidc', methods=['GET'])
@oidc_auth.oidc_auth()
@oidc_auth.oidc_auth(provider_name=PROVIDER_NAME)
def oidc():
user_session = UserSession(flask.session)
app.logger.info(user_session.userinfo)
user = create_or_retrieve_user_from_userinfo(user_session.userinfo)
if user is None:
return ''
login_user(user)
app.logger.info(f"logged in user: {str(user)}")
app.logger.debug(f"id token: {str(user_session.id_token)}")
return jsonify(id_token=user_session.id_token,
access_token=flask.session['access_token'],
userinfo=user_session.userinfo)
@auth_bp.route('/oidc_logout', methods=['GET'])
@oidc_auth.oidc_logout
def oidc_logout():
oidc_auth.oidc_logout()
# oidc_auth.oidc_logout()
app.logger.debug("Logging out current user!")
return redirect('/')
@oidc_auth.error_view
def error(error=None, error_description=None):
app.logger.error(f"Something wwent wrong with OIDC auth error: {error}, message: {error_description}")
return jsonify({'error': error, 'message': error_description})

View File

@@ -10,6 +10,10 @@ PROVIDER_URL = "https://oidc.scc.kit.edu/auth/realms/kit"
PROVIDER_NAME = 'kit_oidc'
PROVIDER_CONFIG = ProviderConfiguration(issuer=PROVIDER_URL,
client_metadata=CLIENT_METADATA,
auth_request_params={'scope': ['openid', 'email', 'profile']})
auth_request_params={'scope': ['openid', 'email']}
# auth_request_params={'scope': ['openid', 'profile']} # avoid to get profile
# -> cookie is getting too large
# auth_request_params={'scope': ['openid', 'email', 'profile']}
)
OIDC_PROVIDERS = {PROVIDER_NAME: PROVIDER_CONFIG}

View File

@@ -2,6 +2,8 @@ import flask_jwt_extended
from flask_jwt_extended import jwt_optional, get_jwt_identity
from functools import wraps
from flask_restx import abort
from backend import jwt_auth
from backend.models.user_model import User
@@ -10,26 +12,16 @@ def requires_permission_level(permission_level):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if flask_jwt_extended.verify_jwt_in_request():
current_user_id = get_jwt_identity()
user = User.get_by_identifier(current_user_id)
if user is not None:
if user.has_permission(permission_level):
#for g in user.groups:
# if g.permissions
#TODO
pass
else:
pass
# return FALSE
#if not session.get('email'):
# return redirect(url_for('users.login'))
#user = User.find_by_email(session['email'])
#elif not user.allowed(access_level):
# return redirect(url_for('users.profile', message="You do not have access to that page. Sorry!"))
# if flask_jwt_extended.verify_jwt_in_request():
current_user_id = get_jwt_identity()
user = User.get_by_identifier(current_user_id)
if user is not None:
if not user.has_permission(permission_level):
abort(401, f"You are missing the permission: {permission_level}")
return f(*args, **kwargs)
return decorated_function
return decorator
@@ -38,5 +30,7 @@ def require_jwt():
@wraps(f)
def decorated_function(*args, **kwargs):
return jwt_auth.login_required(jwt_optional(f(*args, **kwargs)))
return decorated_function
return decorator

33
backend/cert.pem Normal file
View File

@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIJAPBUPeRmjeGxMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV
BAYTAkRFMQswCQYDVQQIDAJCVzELMAkGA1UEBwwCS0ExDDAKBgNVBAoMA0tJVDEM
MAoGA1UECwwDQklCMSgwJgYDVQQDDB91YmthcHMxNTQudWJrYS51bmkta2FybHNy
dWhlLmRlMB4XDTE4MDIwOTEyMTAwOFoXDTE5MDIwOTEyMTAwOFowbTELMAkGA1UE
BhMCREUxCzAJBgNVBAgMAkJXMQswCQYDVQQHDAJLQTEMMAoGA1UECgwDS0lUMQww
CgYDVQQLDANCSUIxKDAmBgNVBAMMH3Via2FwczE1NC51YmthLnVuaS1rYXJsc3J1
aGUuZGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDMIX23STpm+2fC
zX1SoafxhtDwBbHLc7bhhPqiHV2wp92sj8GVr4TRNPtdxMaAtA6PYJJ+rOZggNi5
8ugkQLa9omRRn5Q3V3IQxvUaD5qo0OXI/qHP2xzhBZBzaFwQWfl77kZNibNTYyY0
p9Yueg/fMoO7Xeous6DG2cQ57KfNC7vPzTre5f7wMgMgUHf+ziGrYfP8fMIEP2j7
u2ULaVgrLhgFrVySJBYDfrBoSU5xIy0U26gcKgAfyX441ybRRRtPThmvW9/MIoH+
zRRQAv3SnnnlLH9LAWKEx7OfxjS5Yts8GwJtOJ+TNwO7bIX7TE1+1mZP4hGQHk88
JNO/kHM0el2Cbto703F5x0Nk3KCI5oJ9/A+96Dv/8N9F7bQTpbZa89HcEcuwd2iq
AsyGVlhEZCyhwsfaNC+IDEBGEfgejvZPBUkuXFLR6F1KwoM3+L4RB2FsI9EYNXjw
m/8YCx+2Qn9NoLckf6kJxVYENlLzMWNriyynpmqaQ6XYPPQ1I7OjGqh7jueXV/Bx
bvkrUsXW7zbcAuMxVXW1yH8KuXAdMKLTW5gZ3Dj51agErfiRpc06LPPPHaIaCfT8
cz4pY3IJQ27jv/fbu2SnpGLuZSADPlWuuLvHKVgtrVS6V3Heay7VQJxDXi5Nzfyd
IxGymBfnnjekF4ALJ0f+IZsvDTGojQIDAQABo1MwUTAdBgNVHQ4EFgQU7WjmKeC/
yEqGXTK8Qr2irECh5L4wHwYDVR0jBBgwFoAU7WjmKeC/yEqGXTK8Qr2irECh5L4w
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAxw/iJGQ0zJaYGobl
EmzZX7De/PqiaE/da4Z7Nw2ck5yb0s4z5V5ov41LRWOO/VsaDvP+ucPOrsQzs9uZ
ZYpNdb9X1j1ZLLvTepjVFigJO2tXS08oCanSyt5PFjkH/sfyOvrRNr2VPBSB9RDn
L8Eru1Cbd2PcZFFO+jm8ioFkRz83uXgTP2t7IZxlc0NUAfT252o+7Y3wf2W4zcXS
CiQQTF+isD7F6JVQnmWVLKkBooDE135E3SzesD8bA7VP2OXcld20TMtV6D/HWQKH
s/5HlB/D+02+z1kU09xCbpYiUeAk7pRMkR2doAXZ2XjKu5z47H22WD+pzdXn01kX
rsxcpRUgv9N+wVScGSK01wcBNNRPXijC/k6hIumlwrYuLN0i3VpJx0sOlnZJ34Gv
sVCiW0RDx4mc/HpwMkvNbdKCrra0Q/aX+yjKcAFwelevu0vfj82WF53BAM41aor4
yHCDBpFhrfrtOQm3K1jYbUyqTJXbCwNiNs+wstWmDnfkr0qMebXBBGXF81GLrE/4
s7LgUuyxu3/dX5Hh/yG7p13LszT3xgk1iEVwEWy6w9Op00pzBDMKGR0Yisq4bzdx
ITald/abOFTk+ZwpCOw1HTQO4A6HgwXLAdrVVX45qTP4VsUY8RCcIeEZZtj81SnF
xtchwBqM0lLufxWTUOCBIXMVUuU=
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -12,7 +12,7 @@ from backend import app, main_logger
from apscheduler.schedulers.background import BackgroundScheduler
from backend.cron.cron_state_checker import async_cron_recorder_checker
from backend.cron.cron_state_checker import async_cron_recorder_checker, async_permanent_cron_recorder_checker
from backend.websocket.handlers import send_state_update_to_recorders
cron_log_handler = TimedRotatingFileHandler(app.config.get('CRON_LOG_FILE'), interval=1, when='d', backupCount=3)
@@ -42,10 +42,14 @@ def add_default_jobs(sched=None, testing=False):
if testing:
check_recorder_state_job = sched.add_job(async_cron_recorder_checker.check_object_state, 'interval', seconds=40,
id="check_recorder_state_job")
check_all_recorders_state_job = sched.add_job(async_permanent_cron_recorder_checker, 'interval', minutes=5,
id="check_all_recorders_state_job")
else:
check_recorder_state_job = sched.add_job(async_cron_recorder_checker.check_object_state, 'interval', minutes=2,
id="check_recorder_state_job")
check_all_recorders_state_job = sched.add_job(async_permanent_cron_recorder_checker.check_object_state, 'interval', minutes=30,
id="check_all_recorders_state_job")
"""
Job regularly sending the state to "frontend recorders" through websocket
@@ -54,7 +58,7 @@ def add_default_jobs(sched=None, testing=False):
lambda: send_state_update_to_recorders(async_cron_recorder_checker.get_current_state()), 'interval', minutes=1,
id="send_update_state_to_recorder_job")
return [check_recorder_state_job, send_update_state_to_recorder_job]
return [check_recorder_state_job, send_update_state_to_recorder_job, check_all_recorders_state_job]
def signal_handler(sig, frame):

View File

@@ -9,7 +9,7 @@ from threading import Lock
from typing import Union, Callable, TypeVar, Generic, Set, List
from backend.models import Recorder
from backend.tools.recorder_state_checker import check_capture_agent_state, ping_capture_agent
from backend.tools.recorder_state_checker import check_capture_agent_state, ping_capture_agent, check_stream_sanity
logger = logging.getLogger("lrc.cron.recorder_state")
@@ -97,10 +97,14 @@ class StateChecker(Generic[T]):
object_states[r[2]] = {
'id': object_states[r[2]].get('id', None),
'msg': ", ".join([s for s in [object_states[r[2]].get('msg', None), r[1]] if s]),
'time_stamp': datetime.datetime.now(datetime.timezone.utc).strftime(
"%d.%m.%Y - %H:%M:%S %Z"),
'state_ok': ok}
else:
object_states[r[2]] = {'id': object_states[r[2]].get('id', None),
'msg': r[1],
'time_stamp': datetime.datetime.now(datetime.timezone.utc).strftime(
"%d.%m.%Y - %H:%M:%S %Z"),
'state_ok': False}
except TimeoutError as e:
logger.error("Timeout while performing state check func! {}".format(e))
@@ -163,3 +167,9 @@ class StateChecker(Generic[T]):
async_cron_recorder_checker = StateChecker([check_capture_agent_state, ping_capture_agent], Recorder)
async_permanent_cron_recorder_checker = StateChecker(
[check_capture_agent_state, ping_capture_agent, check_stream_sanity], Recorder)
#for r in Recorder.get_all():
# async_permanent_cron_recorder_checker.add_object_to_state_check(r.id)

52
backend/key.pem Normal file
View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDMIX23STpm+2fC
zX1SoafxhtDwBbHLc7bhhPqiHV2wp92sj8GVr4TRNPtdxMaAtA6PYJJ+rOZggNi5
8ugkQLa9omRRn5Q3V3IQxvUaD5qo0OXI/qHP2xzhBZBzaFwQWfl77kZNibNTYyY0
p9Yueg/fMoO7Xeous6DG2cQ57KfNC7vPzTre5f7wMgMgUHf+ziGrYfP8fMIEP2j7
u2ULaVgrLhgFrVySJBYDfrBoSU5xIy0U26gcKgAfyX441ybRRRtPThmvW9/MIoH+
zRRQAv3SnnnlLH9LAWKEx7OfxjS5Yts8GwJtOJ+TNwO7bIX7TE1+1mZP4hGQHk88
JNO/kHM0el2Cbto703F5x0Nk3KCI5oJ9/A+96Dv/8N9F7bQTpbZa89HcEcuwd2iq
AsyGVlhEZCyhwsfaNC+IDEBGEfgejvZPBUkuXFLR6F1KwoM3+L4RB2FsI9EYNXjw
m/8YCx+2Qn9NoLckf6kJxVYENlLzMWNriyynpmqaQ6XYPPQ1I7OjGqh7jueXV/Bx
bvkrUsXW7zbcAuMxVXW1yH8KuXAdMKLTW5gZ3Dj51agErfiRpc06LPPPHaIaCfT8
cz4pY3IJQ27jv/fbu2SnpGLuZSADPlWuuLvHKVgtrVS6V3Heay7VQJxDXi5Nzfyd
IxGymBfnnjekF4ALJ0f+IZsvDTGojQIDAQABAoICAFtD2gY5WkAyxOhmoVJxbjnh
MccudJhm6uwXXUtf38ScuNJvD3kSGUrD6mK5GJrwZdYaskSqnvGkicFRZhLXUBym
3z1TIJxBn4D6wxjcwyQZCbN8jPH2oAnHSBchIJA6+f07wfjmyONOYAWIyIzNDA2Z
cyYxTgOFUiu1rzLKqRdW2KiGtHx1zi6r3ZP0BkJI/Oq4B7LqQIBPrWtnFD0u4zmj
CSB4qvu34JO9b7egls6kkIJT1uyIprePibx2DSfhQt2JKJirdfq5ru8x15QSUlN5
gTxCUcNCIj8FXjzgDDCajzRnSwV0HHxgS23fjmOVcMx1pRawF3Qc6QYV3Qo31hli
E3fZAFwNkob/adeSPLlly8fFOcLuapMGnLIXYTY4f1bcfu7+x5gIUy17RFlbC0q3
wDxQwopc5AUmJSCvCt2PqSjGfIOcT8mbq9Zc9x7i3FpyarqmUEuSOUi5ufioJEve
lrjyRuBH35vmM565b1UVpsPMPIZoMhGJ9VVvhosfu5QSANv0DyToWAffBbUpXnt4
MnDpJR3S+JpdrYScz/jvIBxhgzrUeYFJ+2YXqFSRLKECwoG3JvBr6lmPP9o+tyyv
nQzsrBZ7IueWfu+w+9d8uPqVrXlJcTXf+6u/WV91DOfJGwfYhimFglMtmyTpq9cb
6oXtq2NiAJjGvPxmJ1GNAoIBAQD66pKZW3l3NWQF1ku+hJaNGU8ZbQ1oCTFXZcM/
bel6CZgVY366deLjUPnJyfe7cRG+c44hN+mvOg+s4GwOI8f/felOpZp66Nqum5+e
RRqP4SiF8Xo/y4NnB//HipeHEEEVQXTOQNewE4uYflFzcYhlm/CXSEfKTdJVMgL8
n+C8tAUeVvm3dyJUjGSRB0RWuaADffRh13XlZokIrE31fVceTCNnLDiwzoiInItl
AUME88apYi/sjS4o1kS2hO2v/0ZsTl6Ibc2peMTs2xHUDzw9Gop8aV0nevNb2Ale
bfvi54fzJiVTIqI69FnfSiceosPmCRyq5SoFdKmHuBDQGEJjAoIBAQDQREGZOec4
zS63qOKHXCzCeHfAQoecSrvJjmcq50JdmfKQqDCcV8MB7ljXToKflJeuNzO4xwiK
xjh+4JUobqlYwyNBQZMxG8Vpmsn8DHSjlx16vkeFTjZP8Nl1ZnVd2HexpyXvHKnM
x8aWmSbE9ZzDM231/q8zuw1jvN69pJuANz8or1wAs+x6NpccS48MBZojziStbQAZ
rH0QsWdOkBwug8YolYi8u9ir9xt+bqR9fU0uhy1V2736Gc2t100iho4vVBA7dAuc
7BSKO+j5SIy1DwI+S8P/hLGpp7bThimcsYkQGo4Wzi3zBGmaV4ZCl4wgqS3TW2EQ
LVlFvoAkp+RPAoIBAQCUSUroHPpU7BW3qWTMLDl5G8r2YM96e2xQlVBlTQSdXcwm
X82GTqMO6k0k5xpkCTeOUWZe1VdiejLHXT4ewSCyKmxWUWJRXwnWBRy5AWfoPg2f
0w0HZLO8kSqld4+Df6Sm9i8csY+GfJaUQZrLWf5c5mKyVUIwGfvC47KGL8o2W0Fn
I8milmKQiwPn/d5yTnN1fNuPczE2qHk1yfasrS1uN1r431aFjxl7euCaibtc0uDG
O8PMzcbI1ZB4OWjrCTdKTKprgFD55eijffg0Veiox+WuDurBqVTnI26uAtvIxkI+
/X0ze5VauAvg/UbPQSv658msaZCC9uY10h8FjEC3AoIBAG+wSyWrIc8aajVgQquo
yPA5vq7CfwtYIMEEt1sQzkx4JNdi+z686f19HvPITNDb1UQ+omQziOczSlTwh805
G2RYse1rB07Mv9/UfQHIhDy+67ZJmP1qZkIUvenx2ntLfVUueG91BbKmaF+XHm19
8mXUjOHhhX/Ojm2wehtlzWbDOgHNmR9fXjBkWkF4W+xsjK8q/AxtaiJamG99VBOT
wSlIzdox5zSf4KDIUlxJZblOmzeakrt6rrUTZXQXBGzBkDrdcB8SKrArAxDm9BfL
ynnG5MMXyrKbLNP491kUl/hKVWDnfM/KHmY3NZLp4TyRHTrev46bcMBGMZvvf7Uo
vI0CggEAdL9KcgPrK6/3DtaY2XcQAUjrMXjnthgJWUtZkdtZ7xQrLIaRMW0FSgQd
5U3eoHrXTmnFCRSTE87BA1jUPlZ/iaQw9Spfuz86IYoyu+lLOGfhaD/qVZ3lWUj4
/S95K/9561PDflWqO2BQ77UhfrXOyLPFsvUpJspZ7RuIrX0OPWplLlTRAM2zEJOd
Q66dS3Wle7Xkz0ypo4HWzYc4uqHHtbGEVmCLlqxLJFBjtlHFAbLY//PdiU/nMGWB
MYka0asxUegydpH/t6FZD426lhDp8P2KB7xk4XV47s2KfIsemeteE4vVFT+wRnel
MyJDELvA01ZFZW9xXMwfxTk7k7fUCA==
-----END PRIVATE KEY-----

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env python
import os, sys
from backend.models import Permission, Group
sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir))
import os
import unittest
@@ -59,10 +62,30 @@ def cov():
return 1
def insert_initial_groups():
print("DB: inserting default groups:")
for g in app.config.get("GROUPS", []):
print(g['name'])
g_permissions = g.pop('permissions', [])
g['permissions'] = Permission.get_by_names(g_permissions)
db.session.add(Group(**g))
db.session.commit()
@manager.command
def recreate_db():
"""Drops the db tables."""
db.drop_all()
"""Creates the db tables."""
db.create_all()
insert_initial_groups()
@manager.command
def create_db():
"""Creates the db tables."""
db.create_all()
insert_initial_groups()
@manager.command

View File

@@ -10,7 +10,6 @@ from sqlalchemy.exc import IntegrityError
from datetime import datetime, timedelta
from backend import db, app, login_manager
from backend.tools.scrape_rooms import scrape_rooms
metadata = MetaData()

View File

@@ -20,7 +20,9 @@ from sqlalchemy import or_
from datetime import datetime, timedelta
from backend.models.model_base import ModelBase
from backend.models.virtual_command_model import virtual_command_recorder_command_table, virtual_command_recorder_table
from backend.models.virtual_command_model import virtual_command_recorder_command_table, \
virtual_command_recorder_table, \
virtual_command_recorder_model_table
metadata = MetaData()
@@ -73,6 +75,8 @@ class RecorderModel(db.Model, ModelBase):
checksum = db.Column(db.String(63), unique=True,
nullable=False) # checksum of the recorder commands! (see: model_updater.py)
last_checksum_change = db.Column(db.DateTime, nullable=True, default=None)
virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_model_table,
back_populates='recorder_model_commands')
_requires_user = db.Column(db.Integer, default=False, name='requires_user')
_requires_password = db.Column(db.Integer, default=True, name='requires_password')

View File

@@ -5,15 +5,15 @@ Models for lecture recorder
import json
import logging
from sqlalchemy import MetaData, CheckConstraint
from sqlalchemy import MetaData, CheckConstraint, UniqueConstraint
from sqlalchemy.exc import IntegrityError
from datetime import datetime, timedelta
from backend import db, app, login_manager
from backend.tools.scrape_rooms import scrape_rooms
from backend.tools.campus_rooms import get_campus_rooms
logger = logging.getLogger("lrc."+__name__)
logger = logging.getLogger("lrc." + __name__)
metadata = MetaData()
@@ -23,16 +23,19 @@ class Room(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
name = db.Column(db.Unicode(127), unique=False, nullable=False)
alternate_name = db.Column(db.Unicode(127), unique=False, nullable=True, default=None)
external_id = db.Column(db.String, nullable=True, unique=True)
coordinates = db.Column(db.String, nullable=True)
comment = db.Column(db.Unicode(2047), unique=False, nullable=True, default="")
number = db.Column(db.Unicode(63), unique=False, nullable=True)
number = db.Column(db.Unicode(63), unique=False, nullable=True, default=None)
building_name = db.Column(db.Unicode(63), unique=False, nullable=True)
building_number = db.Column(db.Unicode(63), unique=False, nullable=True)
building_number = db.Column(db.Unicode(63), unique=False, nullable=True, default=None)
recorder = db.relationship('Recorder', uselist=False, back_populates='room') # one-to-one relation (uselist=False)
__table_args__ = (
CheckConstraint('length(name) > 2',
name='name_min_length'),
UniqueConstraint('name', 'number', 'building_number', 'external_id'),
)
def __init__(self, **kwargs):
@@ -76,13 +79,15 @@ class Room(db.Model):
def pre_fill_table():
rooms = scrape_rooms()
logger.debug("tada")
# Room.query.delete() # drop all rooms
rooms = get_campus_rooms()
logger.debug("got {} rooms".format(len(rooms)))
db_rooms = [Room(name=room['name'], number=room['room_number'],
building_name=room['building_name'], building_number=room['building_number']) for room in
rooms]
db_rooms = [Room(name=room['name'], number=room['room_number'], alternate_name=room.get('alternate_name', None),
building_name=room['building_name'], building_number=room['building_number'],
coordinates=room.get('coordinates', None), external_id=room.get('external_id', None)
) for room in
rooms]
try:
db.session.bulk_save_objects(db_rooms)
db.session.commit()

View File

@@ -3,9 +3,11 @@
Example user model and related models
"""
import json
from enum import Enum
from sqlalchemy.orm import relation
from sqlalchemy import MetaData
import sqlalchemy
from sqlalchemy.orm import relation, validates
from sqlalchemy import MetaData, any_
from backend import db, app, login_manager
from backend.config import Config
@@ -19,6 +21,7 @@ from datetime import datetime, timedelta
from passlib.hash import sha256_crypt
from hashlib import md5
metadata = MetaData()
followers = db.Table('followers',
@@ -74,16 +77,16 @@ group_permission_table = db.Table('group_permission',
# This is the association table for the many-to-many relationship between
# users and permissions.
user_permission_table = db.Table('user_permission',
db.Column('user_id', db.Integer,
db.ForeignKey('user.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True),
db.Column('permission_id', db.Integer,
db.ForeignKey('permission.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True))
db.Column('user_id', db.Integer,
db.ForeignKey('user.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True),
db.Column('permission_id', db.Integer,
db.ForeignKey('permission.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True))
class User(UserMixin, db.Model):
@@ -108,6 +111,7 @@ class User(UserMixin, db.Model):
password = db.Column(db.String(255), nullable=True)
registered_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
external_user = db.Column(db.Boolean, default=False)
external_user_id = db.Column(db.Unicode(63), unique=True, nullable=True, default=None)
last_seen = db.Column(db.DateTime, default=datetime.utcnow())
last_time_modified = db.Column(db.DateTime, default=datetime.utcnow())
jwt_exp_delta_seconds = db.Column(db.Integer, nullable=True)
@@ -135,6 +139,11 @@ class User(UserMixin, db.Model):
if external_user is not None:
self.external_user = external_user
@validates('email')
def validate_address(self, key, email):
assert re.match(r"[^@]+@[^@]+\.[^@]+", email), "email is invalid"
return email
@staticmethod
@login_manager.user_loader
def get_by_identifier(identifier):
@@ -200,6 +209,8 @@ class User(UserMixin, db.Model):
return None
user = cls.query.filter_by(email=email).first()
if not user:
user = cls.query.filter_by(nickname=email).first() # be nice and allow nickname as well...
if not user or not user.verify_password(password):
return None
@@ -243,14 +254,24 @@ class User(UserMixin, db.Model):
@property
def effective_permissions(self):
permissions = Config.ROLE_PERMISSION_MAPPINGS.get(self.role, [])
role_permissions = Config.ROLE_PERMISSION_MAPPINGS.get(self.role, set())
permissions = set(Permission.query.filter(Permission.name.in_(role_permissions)).all())
for g in self.groups:
print(g)
for p in g.permissions:
print(p)
permissions.append(p)
permissions.add(p)
return permissions
def has_permission(self, permission):
user_permissions = self.effective_permissions
if isinstance(permission, str):
return any([user_permission.name == permission for user_permission in user_permissions])
if isinstance(permission, Permission):
return any([user_permission.id == permission.id for user_permission in user_permissions])
if isinstance(permission, Enum):
return any([user_permission.name == str(permission.value) for user_permission in user_permissions])
return False
@staticmethod
def decode_auth_token(auth_token):
"""
@@ -497,14 +518,13 @@ class Permission(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.Unicode(63), unique=True, nullable=False)
description = db.Column(db.Unicode(511))
#read_only = db.Column(db.Boolean, default=False)
# read_only = db.Column(db.Boolean, default=False)
groups = db.relationship(Group, secondary=group_permission_table,
back_populates='permissions')
users = db.relationship(User, secondary=user_permission_table,
back_populates='permissions')
back_populates='permissions')
access_control_entry = db.relationship('AccessControlEntry', back_populates='required_permission')
@staticmethod
def get_by_name(name):
"""
@@ -514,6 +534,17 @@ class Permission(db.Model):
"""
return Permission.query.filter(Permission.name == name).first()
@staticmethod
def get_by_names(names: list):
"""
Find permissions by their names
:param names:
:return:
"""
if len(names) < 1:
return []
return Permission.query.filter(or_(*[Permission.name.like(name) for name in names])).all()
@staticmethod
def get_all():
"""
@@ -522,22 +553,42 @@ class Permission(db.Model):
"""
return Permission.query.all()
@event.listens_for(Permission.__table__, 'after_create')
def insert_initial_permissions(*args, **kwargs):
print("DB: inserting default permissions:")
for p in app.config.get("PERMISSIONS", []):
print(p)
db.session.add(Permission(name=p))
db.session.commit()
# insert_initial_groups() # call this function here again, as often (always?) permission table does not yet exist
@event.listens_for(User.__table__, 'after_create')
def insert_initial_users(*args, **kwargs):
print("DB: inserting default users:")
for u in app.config.get("USERS", []):
db.session.add(User(**u))
db.session.commit()
# The following initialization does not work as it depends on the existence of multiple tables
# This initialization has now been moved to manage.py!
"""
@event.listens_for(Group.__table__, 'after_create')
def insert_initial_groups(*args, **kwargs):
for g in app.config.get("GROUPS", []):
db.session.add(Group(**g))
db.session.commit()
@event.listens_for(Permission.__table__, 'after_create')
def insert_initial_permissions(*args, **kwargs):
for p in app.config.get("PERMISSIONS", []):
db.session.add(Permission(name=p))
db.session.commit()
print("DB: inserting default groups:")
try:
for g in app.config.get("GROUPS", []):
print(g['name'])
g_permissions = g.pop('permissions', [])
g['permissions'] = Permission.get_by_names(g_permissions)
print(g['permissions'])
db.session.add(Group(**g))
db.session.commit()
except sqlalchemy.exc.OperationalError as e:
first_error_line = str(e).split('\n')[0]
if "no such table" not in first_error_line:
raise
print(f"Permission table probably does not exist yet: {first_error_line} - you can probably ignore this!")
"""

View File

@@ -34,6 +34,24 @@ virtual_command_recorder_table = db.Table('virtual_command_recorder',
ondelete="CASCADE"),
primary_key=True))
# probably useless!!
# This is the association table for the many-to-many relationship between
# virtual commands and recorder commands.
virtual_command_recorder_model_table = db.Table('virtual_command_recorder_model',
db.Column('virtual_command_id', db.Integer,
db.ForeignKey('virtual_command.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True),
db.Column('recorder_model_id', db.Integer,
db.ForeignKey('recorder_model.id',
onupdate="CASCADE",
ondelete="CASCADE"),
primary_key=True))
# probably useless!!
class VirtualCommand(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
@@ -47,12 +65,19 @@ class VirtualCommand(db.Model):
recorder_commands = db.relationship('RecorderCommand', secondary=virtual_command_recorder_command_table,
back_populates='virtual_commands')
_command_default_params_json_string = db.Column(db.UnicodeText, default='')
# probably useless!!
recorder_model_commands = db.relationship('RecorderModel', secondary=virtual_command_recorder_model_table,
back_populates='virtual_commands')
# probably useless!!
# parent_virtual_command = db.relationship('VirtualCommand', back_populates='child_virtual_commands')
parent_virtual_command_id = db.Column(db.Integer, db.ForeignKey('virtual_command.id'))
child_virtual_commands = db.relationship('VirtualCommand',
backref=backref('parent_virtual_command', remote_side=[id]))
command_order_string = db.Column(db.String)
_command_order_json_string = db.Column(db.String, default='')
def __init__(self, **kwargs):
super(VirtualCommand, self).__init__(**kwargs)
@@ -76,13 +101,26 @@ class VirtualCommand(db.Model):
@hybrid_property
def command_order(self):
if self.command_order_string is None:
if self._command_order_json_string is None:
return []
return json.loads(self.command_order_string)
return json.loads(self._command_order_json_string)
@command_order.setter
def command_order(self, ordered_list_of_commands: list):
self.command_order_string = json.dumps(ordered_list_of_commands)
self._command_order_json_string = json.dumps(ordered_list_of_commands)
@hybrid_property
def command_default_params(self) -> list:
return json.loads(self._command_default_params_json_string)
@command_default_params.setter
def command_default_params(self, value: list):
self._command_default_params_json_string = json.dumps(value)
def add_command_default_param(self, param_name: str, value: str):
command_default_params = json.loads(self._command_default_params_json_string)
command_default_params.append({'param_name': param_name, 'value': value})
self._command_default_params_json_string = json.dumps(command_default_params)
def __str__(self):
return self.name

View File

@@ -10,6 +10,7 @@ from flask_pyoidc.user_session import UserSession
from backend import app
from backend.auth import oidc_auth
from backend.auth.oidc_config import PROVIDER_NAME
fe_path = os.path.abspath(os.path.join(app.root_path, os.pardir, os.pardir, "frontend", "dist"))
if not os.path.exists(fe_path) or not os.path.exists(os.path.join(fe_path, "index.html")):
@@ -37,7 +38,7 @@ def send_img(path):
@fe_bp.route('/test')
@oidc_auth.oidc_auth()
@oidc_auth.oidc_auth(provider_name=PROVIDER_NAME)
def test_oidc():
user_session = UserSession(flask.session)
access_token = user_session.access_token

View File

@@ -0,0 +1,53 @@
import re
from campus_api_client.campus_management_api_client import CampusManagementApiClient
from backend import Config
def get_campus_rooms():
cac = CampusManagementApiClient(user=Config.CAMPUS_MANAGEMENT_USER, pw=Config.CAMPUS_MANAGEMENT_PW)
parsed_rooms = []
count = 0
re_string = r"^(\d\d.\d\d)?\s(.*)"
re_exp = re.compile(re_string)
for r in cac.get_rooms():
name = r.get('name')
# print(f"## {r.get('name')}: {r.get('roomNo')}")
building_number = r.get('buildingNo', None)
building_name = r.get('building')
if building_number is None or building_number == '':
count += 1
# print(r)
match = re_exp.match(building_name)
if match is not None:
building_number, building_name = match.groups()
else:
try:
building_name = building_name.split(building_number)[1].strip()
building_name = None if building_name == "" else building_name
except IndexError: # ignore index error
pass
# print(f"Building No {r.get('buildingNo')}: {r.get('building')}")
# print(building_name)
# print(building_number)
if building_number is not None and building_number != "" and name.startswith(building_number):
name = name.split(building_number, 1)[1].strip()
elif building_number is not None and name.startswith(f"Geb. {building_number}"):
name = name.split(f"Geb. {building_number}", 1)[1].strip()
room = {'name': name,
'alternate_name': r.get('internalName'),
'external_id': r.get('guid'),
'room_number': r.get('roomNo'),
'building_name': building_name,
'building_number': building_number,
'coordinates': r.get('coordinates')}
parsed_rooms.append(room)
return parsed_rooms
if __name__ == "__main__":
rooms = get_campus_rooms()
print(len(rooms))

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,7 @@
from backend import LrcException
import logging
logger = logging.getLogger("lrc.tools.exception_decorator")
def exception_decorator(*exceptions):
@@ -8,6 +11,9 @@ def exception_decorator(*exceptions):
ret = func(*args, **kwargs)
return ret
except exceptions as e:
logger.error(str(e))
raise LrcException(e)
return new_func
return decorator

View File

@@ -170,6 +170,22 @@ def create_default_recorders():
db.session.commit()
def add_test_recorder():
test_rec_mac = "00:05:A6:16:A3:E4"
rec = Recorder.get_by_mac(test_rec_mac)
if rec is None:
rec = Recorder()
rec.mac = test_rec_mac
rec.name = "Test SMP (MZ)"
rec.model_name = "SMP 352"
rec.recorder_model = RecorderModel.get_where_adapter_id_contains("SMP 35")
rec.username = "admin"
rec.password = "123mzsmp"
rec.firmware_version = "2.11b0"
rec.ip = "172.22.246.207"
db.session.commit()
if __name__ == '__main__':
# for r in Room.get_all():
# print(r)

View File

@@ -11,6 +11,7 @@ from typing import Union
import requests
from requests.auth import HTTPBasicAuth
from requests.exceptions import ConnectionError
from multiprocessing.pool import ThreadPool
from multiprocessing.context import TimeoutError
@@ -26,6 +27,8 @@ from backend.recorder_adapters.extron_smp import SMP35x
from backend.tools.recorder_streams_sanity_checks import check_frame_is_valid, check_if_audio_is_valid
from backend.tools.send_mail import send_error_mail, get_smtp_error_handler
from backend.tools.exception_decorator import exception_decorator
logger = logging.getLogger("lrc.tools.simple_state_checker")
smtp_error_handler = get_smtp_error_handler(subject="Errors have been detected while checking recorder states!")
@@ -50,6 +53,7 @@ agent_states_lock = threading.RLock()
agent_states = {}
@exception_decorator(ConnectionError)
def get_service_url(service_type: str):
if service_type in config['service_urls']:
return config['service_urls'][service_type]
@@ -64,19 +68,23 @@ def get_service_url(service_type: str):
return None
@exception_decorator(ConnectionError)
def get_calender(rec_id):
params = {'agentid': rec_id}
url = get_service_url('org.opencastproject.scheduler') + "/calendars"
res = session.get(url, params=params)
if res.ok:
return Calendar(res.text)
raise LrcException(res.text, res.status_code)
@exception_decorator(ConnectionError)
def get_capture_agents():
url = get_service_url("org.opencastproject.capture.admin") + "/agents.json"
res = session.get(url)
if res.ok:
return res.json()["agents"]["agent"]
raise LrcException(res.text, res.status_code)
def get_recorder_details_old():
@@ -117,19 +125,25 @@ def get_recorder_adapter(recorder_info: Union[dict, Recorder]) -> RecorderAdapte
type = recorder_info.get("type")
except KeyError:
type = RecorderModel.get_by_id(recorder_info.get('recorder_model_id')).model_name
if "SMP" in type:
rec = SMP35x(recorder_info.get('ip'), recorder_info.get('password'))
else:
rec = Epiphan(recorder_info.get('ip'), recorder_info.get("username"), recorder_info.get("password"))
return rec
try:
if "SMP" in type:
rec = SMP35x(recorder_info.get('ip'), recorder_info.get('password'))
else:
rec = Epiphan(recorder_info.get('ip'), recorder_info.get("username"), recorder_info.get("password"))
return rec
except LrcException:
raise
def check_stream_sanity(recorder_agent: Union[Recorder, dict], recorder_adapter: RecorderAdapter = None):
if recorder_adapter is None:
recorder_info = get_recorder_by_name(recorder_agent.get('name'))
recorder_adapter = get_recorder_adapter(recorder_info)
if not recorder_adapter.is_recording():
return True, "not recording, so there is no stream!", recorder_agent.get('name')
try:
if recorder_adapter is None:
recorder_info = get_recorder_by_name(recorder_agent.get('name'))
recorder_adapter = get_recorder_adapter(recorder_info)
if not recorder_adapter.is_recording():
return True, "not recording, so there is no stream!", recorder_agent.get('name')
except LrcException:
return False, "Could not determine if recorder is recording!", recorder_agent.get('name')
if recorder_agent.get('archive_stream1') is None and recorder_agent.get(
'archive_stream2') is None: # fall back to default names and rtsp
archive_stream_1_url = "rtsp://{}/{}".format(recorder_adapter.address, Config.DEFAULT_ARCHIVE_STREAM_1_NAME)
@@ -186,7 +200,12 @@ def check_capture_agent_state(recorder_agent: Union[Recorder, dict]):
return True, "Recorder is in offline / maintenance mode", recorder_agent.get('name')
agent_state_error_msg = None
logger.debug("Checking Agent {}".format(recorder_agent.get('name')))
c = get_calender(recorder_agent.get('name'))
try:
c = get_calender(recorder_agent.get('name'))
except LrcException:
error_msg = "Could not get calender of recorder agent: {}!".format(recorder_agent.get('name'))
logger.fatal(error_msg)
return False, error_msg, recorder_agent.get('name')
is_recording_in_calendar = len(list(c.timeline.now())) >= 1
if is_recording_in_calendar:
logger.info("{} has entry in Calender and should therefore be recording... checking now!".format(

View File

@@ -21,17 +21,17 @@ class WebSocketBase:
self.flask_app_context = app
self.socket_thread = None
def start_websocket_in_thread(self, host=None, port=None, debug=None):
def start_websocket_in_thread(self, host=None, port=None, debug=None, **kwargs):
self.socket_thread = threading.Thread(
target=self.start_websocket,
args=(host, port, debug))
args=(host, port, debug, kwargs))
self.socket_thread.start()
return self.socket_thread
def start_websocket(self, host=None, port=None, debug=None):
def start_websocket(self, host=None, port=None, debug=None, **kwargs):
if debug is None:
debug = self.flask_app_context.debug
socketio.run(self.flask_app_context, host=host, port=port, debug=debug)
socketio.run(self.flask_app_context, host=host, port=port, debug=debug, **kwargs)
def send_test_msg(self):
socketio.emit('test', "tolle nachricht")

1
current_rooms_cache.json Normal file

File diff suppressed because one or more lines are too long

1
migrations/README Normal file
View File

@@ -0,0 +1 @@
Generic single-database configuration.

45
migrations/alembic.ini Normal file
View File

@@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

96
migrations/env.py Normal file
View File

@@ -0,0 +1,96 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

2485
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

53
pyproject.toml Normal file
View File

@@ -0,0 +1,53 @@
[tool.poetry]
name = "lrc-backend"
version = "0.1.0"
description = ""
authors = ["Tobias K. <kurze@kit.edu>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
ffmpeg-python = "*"
flask = "*"
flask-httpauth = "*"
flask-sqlalchemy = "*"
flask-login = "*"
pyjwt = "*"
passlib = "*"
sqlalchemy = "*"
sqlalchemy-migrate = "*"
flask-script = "*"
flask-migrate = "*"
coverage = "*"
flask-testing = "*"
flask-pyoidc = "*"
python-jose = "*"
flask-jwt-extended = "*"
ssh2-python = "*"
update = "*"
flask-cors = "*"
html5lib = "*"
beautifulsoup4 = "*"
flask-socketio = "*"
eventlet = "*"
ics = "*"
coloredlogs = "*"
pythonping = "*"
scapy = "*"
python-socketio = {version = "*", extras = ["client"]}
socketio-client = "*"
websocket-client = "*"
apscheduler = "*"
pillow = "*"
pydub = "*"
simpleaudio = "*"
flask-restx = "*"
campus_api_client = {git = "git@gitlab.kit.edu:kit/bib/it/tobias/campus_api_client.git"}
#cac = {git = "git@gitlab.kit.edu:kit/bib/it/tobias/campus_api_client.git"}
[tool.poetry.group.dev.dependencies]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"