Compare commits
15 Commits
production
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
466cb344e6 | ||
| 3ae6db1184 | |||
| b18f9ba616 | |||
|
|
c8a517ff60 | ||
|
|
9c19708381 | ||
| 160076afcf | |||
|
|
437cec38e0 | ||
|
|
82b3e78488 | ||
|
|
16e4231807 | ||
| dc142bca0c | |||
|
|
cc334f1727 | ||
| de398d189a | |||
|
|
1d4c4c8ec2 | ||
|
|
6b50334741 | ||
|
|
bd8e60bf5d |
1
Pipfile
1
Pipfile
@@ -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
640
Pipfile.lock
generated
@@ -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": {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, '/')
|
||||
|
||||
@@ -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
BIN
backend/app.db_debug
Normal file
Binary file not shown.
BIN
backend/app.db_old
Normal file
BIN
backend/app.db_old
Normal file
Binary file not shown.
BIN
backend/app.db_test
Normal file
BIN
backend/app.db_test
Normal file
Binary file not shown.
@@ -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})
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
33
backend/cert.pem
Normal 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.
@@ -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):
|
||||
|
||||
@@ -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
52
backend/key.pem
Normal 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-----
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!")
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
53
backend/tools/campus_rooms.py
Normal file
53
backend/tools/campus_rooms.py
Normal 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))
|
||||
1
backend/tools/current_rooms_cache.json
Normal file
1
backend/tools/current_rooms_cache.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
1
current_rooms_cache.json
Normal file
File diff suppressed because one or more lines are too long
1
migrations/README
Normal file
1
migrations/README
Normal file
@@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
45
migrations/alembic.ini
Normal file
45
migrations/alembic.ini
Normal 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
96
migrations/env.py
Normal 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
24
migrations/script.py.mako
Normal 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
2485
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
pyproject.toml
Normal file
53
pyproject.toml
Normal 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"
|
||||
Reference in New Issue
Block a user