From 068cfa17459b678d7d22a22cce5d45281db64f19 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Tue, 10 Nov 2020 18:41:49 -0500 Subject: [PATCH 01/44] Re-implement server-side session and session timeout --- .env.example | 1 + Pipfile | 117 ++ Pipfile.lock | 1300 +++++++++++++++++ app/__init__.py | 13 +- app/auth/utils.py | 2 +- app/auth/views.py | 8 +- app/celery_config.py | 5 + app/jobs.py | 32 +- app/lib/redis_utils.py | 27 - app/main/views.py | 3 +- .../plugins/bootstrap-session-timeout.min.js | 1 - app/static/js/plugins/session-timeout.js | 1 + app/templates/base.html | 2 +- app/templates/base.js.html | 29 +- config.py | 7 +- 15 files changed, 1478 insertions(+), 70 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100755 app/static/js/plugins/bootstrap-session-timeout.min.js create mode 100644 app/static/js/plugins/session-timeout.js diff --git a/.env.example b/.env.example index 6c17125f7..93a21e96c 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ BASE_URL= VIEW_REQUEST_ENDPOINT= APP_TIMEZONE= SESSION_COOKIE_SECURE=True +SESSION_TYPE= ERROR_RECIPIENTS= # Log Settings diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..7cbe4b951 --- /dev/null +++ b/Pipfile @@ -0,0 +1,117 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +pipdeptree = "==0.9.0" +decorator = "==4.0.11" +Faker = "==0.7.9" +ipdb = "==0.10.2" +ipython = "==5.3.0" +ipython-genutils = "==0.2.0" +pexpect = "==4.2.1" +pickleshare = "==0.7.4" +prompt-toolkit = "==1.0.13" +ptyprocess = "==0.5.1" +Pygments = "==2.2.0" +simplegeneric = "==0.8.1" +traitlets = "==4.3.2" +wcwidth = "==0.1.7" +pytest = "==3.6.0" +pytest-cov = "==2.5.1" +atomicwrites = "==1.3.0" +attrs = "==18.2.0" +more-itertools = "==5.0.0" +pluggy = "==0.6.0" +py = "==1.7.0" + +[packages] +alembic = "==1.0.7" +anyjson = "==0.3.3" +appdirs = "==1.4.3" +asn1crypto = "==0.24.0" +bcrypt = "==3.1.6" +blinker = "==1.4" +cached-property = "==1.5.1" +cairocffi = "==0.9.0" +certifi = "==2018.11.29" +cffi = "==1.11.5" +chardet = "==3.0.4" +click = "==7.0" +coverage = "==4.5.2" +cryptography = "==2.5" +cssselect2 = "==0.2.1" +defusedxml = "==0.5.0" +dominate = "==2.3.5" +elasticsearch = "==6.3.1" +holidays = "==0.9.9" +html5lib = "==1.0.1" +idna = "==2.8" +isodate = "==0.6.0" +itsdangerous = "==1.1.0" +jsonschema = "==2.6.0" +ldap3 = "==2.5.2" +lxml = "==4.3.0" +oauthlib = "==3.0.1" +paramiko = "==2.4.2" +pdfrw = "==0.4" +pkgconfig = "==1.4.0" +pyasn1 = "==0.4.5" +pycparser = "==2.19" +pyparsing = "==2.3.1" +python-dateutil = "==2.8.0" +python-dotenv = "==0.10.1" +python-editor = "==1.0.4" +pytz = "==2018.9" +raven = "==6.10.0" +redis = "==3.1.0" +requests = "==2.21.0" +requests-oauthlib = "==1.2.0" +simplekv = "==0.11.11" +six = "==1.12.0" +tablib = "==0.13.0" +tinycss2 = "==0.6.1" +tzlocal = "==1.5.1" +urllib3 = "==1.24.1" +virtualenv = "==13.1.2" +visitor = "==0.1.3" +webencodings = "==0.5.1" +xmlsec = "==1.3.3" +business_calendar = "==0.2.1" +CairoSVG = "==2.3.0" +Flask = "==1.0.2" +Flask-Bootstrap = "==3.3.7.1" +Flask-Elasticsearch = "==0.2.5" +Flask-Login = "==0.4.1" +Flask-Mail = "==0.9.1" +Flask-Migrate = "==2.3.1" +Flask-Moment = "==0.6.0" +Flask-reCaptcha = "==0.4.2" +Flask-Script = "==2.0.6" +Flask-SQLAlchemy = "==2.3.2" +Flask-Tracy = "==0.1.3" +Flask-WeasyPrint = "==0.5" +Flask-WTF = "==0.14.2" +Jinja2 = "==2.10" +Mako = "==1.0.7" +MarkupSafe = "==1.1.0" +Pillow = "==5.4.1" +PyNaCl = "==1.3.0" +pyOpenSSL = "==19.0.0" +Pyphen = "==0.9.5" +SQLAlchemy = "==1.2.17" +WeasyPrint = "==45" +Werkzeug = "==0.14.1" +WTForms = "==2.2.1" +psycopg2-binary = "*" +python-magic-bin = "==0.4.14" +flask-session = "*" +celery = "*" +billiard = "*" +kombu = "*" +vine = "*" +amqp = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..2995aa757 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1300 @@ +{ + "_meta": { + "hash": { + "sha256": "c985a47a5c3cdc822ec0b89b975ccafdc418ea906740f127dcd73607667865fc" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "alembic": { + "hashes": [ + "sha256:16505782b229007ae905ef9e0ae6e880fddafa406f086ac7d442c1aaf712f8c2" + ], + "index": "pypi", + "version": "==1.0.7" + }, + "amqp": { + "hashes": [ + "sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a", + "sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784" + ], + "index": "pypi", + "version": "==5.0.2" + }, + "anyjson": { + "hashes": [ + "sha256:37812d863c9ad3e35c0734c42e0bf0320ce8c3bed82cd20ad54cb34d158157ba" + ], + "index": "pypi", + "version": "==0.3.3" + }, + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "index": "pypi", + "version": "==1.4.3" + }, + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "index": "pypi", + "version": "==0.24.0" + }, + "backports.csv": { + "hashes": [ + "sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d", + "sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce" + ], + "version": "==1.0.7" + }, + "bcrypt": { + "hashes": [ + "sha256:0ba875eb67b011add6d8c5b76afbd92166e98b1f1efab9433d5dc0fafc76e203", + "sha256:21ed446054c93e209434148ef0b362432bb82bbdaf7beef70a32c221f3e33d1c", + "sha256:28a0459381a8021f57230954b9e9a65bb5e3d569d2c253c5cac6cb181d71cf23", + "sha256:2aed3091eb6f51c26b7c2fad08d6620d1c35839e7a362f706015b41bd991125e", + "sha256:2fa5d1e438958ea90eaedbf8082c2ceb1a684b4f6c75a3800c6ec1e18ebef96f", + "sha256:3a73f45484e9874252002793518da060fb11eaa76c30713faa12115db17d1430", + "sha256:3e489787638a36bb466cd66780e15715494b6d6905ffdbaede94440d6d8e7dba", + "sha256:44636759d222baa62806bbceb20e96f75a015a6381690d1bc2eda91c01ec02ea", + "sha256:678c21b2fecaa72a1eded0cf12351b153615520637efcadc09ecf81b871f1596", + "sha256:75460c2c3786977ea9768d6c9d8957ba31b5fbeb0aae67a5c0e96aab4155f18c", + "sha256:8ac06fb3e6aacb0a95b56eba735c0b64df49651c6ceb1ad1cf01ba75070d567f", + "sha256:8fdced50a8b646fff8fa0e4b1c5fd940ecc844b43d1da5a980cb07f2d1b1132f", + "sha256:9b2c5b640a2da533b0ab5f148d87fb9989bf9bcb2e61eea6a729102a6d36aef9", + "sha256:a9083e7fa9adb1a4de5ac15f9097eb15b04e2c8f97618f1b881af40abce382e1", + "sha256:b7e3948b8b1a81c5a99d41da5fb2dc03ddb93b5f96fcd3fd27e643f91efa33e1", + "sha256:b998b8ca979d906085f6a5d84f7b5459e5e94a13fc27c28a3514437013b6c2f6", + "sha256:dd08c50bc6f7be69cd7ba0769acca28c846ec46b7a8ddc2acf4b9ac6f8a7457e", + "sha256:de5badee458544ab8125e63e39afeedfcf3aef6a6e2282ac159c95ae7472d773", + "sha256:ede2a87333d24f55a4a7338a6ccdccf3eaa9bed081d1737e0db4dbd1a4f7e6b6" + ], + "index": "pypi", + "version": "==3.1.6" + }, + "billiard": { + "hashes": [ + "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", + "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" + ], + "index": "pypi", + "version": "==3.6.3.0" + }, + "blinker": { + "hashes": [ + "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" + ], + "index": "pypi", + "version": "==1.4" + }, + "business-calendar": { + "hashes": [ + "sha256:2f91a0a41c8b7bd641afa33befc6e8d5e871beb16c827bc2d508f1025e78c635", + "sha256:ed434c389c277438592df7285fde5882d3c26c6d9658a9cd3c35adee4d136d6d" + ], + "index": "pypi", + "version": "==0.2.1" + }, + "cached-property": { + "hashes": [ + "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", + "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" + ], + "index": "pypi", + "version": "==1.5.1" + }, + "cachelib": { + "hashes": [ + "sha256:47e95a67d68c729cbad63285a790a06f0e0d27d71624c6e44c1ec3456bb4476f", + "sha256:7df6e05b8dfccdeb869e171575580e5606cf1e82a166633b3cb406bc4f113fd0" + ], + "version": "==0.1.1" + }, + "cairocffi": { + "hashes": [ + "sha256:15386c3a9e08823d6826c4491eaccc7b7254b1dc587a3b9ce60c350c3f990337" + ], + "index": "pypi", + "version": "==0.9.0" + }, + "cairosvg": { + "hashes": [ + "sha256:07190b3d473a33695cda4fafee085922141ae87836303013dd4dcdebcba886a9", + "sha256:66f333ef5dc79fdfbd3bbe98adc791b1f854e0461067d202fa7b15de66d517ec" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "celery": { + "hashes": [ + "sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf", + "sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6" + ], + "index": "pypi", + "version": "==5.0.2" + }, + "certifi": { + "hashes": [ + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + ], + "index": "pypi", + "version": "==2018.11.29" + }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "index": "pypi", + "version": "==1.11.5" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "index": "pypi", + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "index": "pypi", + "version": "==7.0" + }, + "click-didyoumean": { + "hashes": [ + "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb" + ], + "version": "==0.0.3" + }, + "click-repl": { + "hashes": [ + "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5", + "sha256:b9f29d52abc4d6059f8e276132a111ab8d94980afe6a5432b9d996544afa95d5" + ], + "version": "==0.1.6" + }, + "coverage": { + "hashes": [ + "sha256:06123b58a1410873e22134ca2d88bd36680479fe354955b3579fb8ff150e4d27", + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:0d34245f824cc3140150ab7848d08b7e2ba67ada959d77619c986f2062e1f0e8", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:258b21c5cafb0c3768861a6df3ab0cfb4d8b495eee5ec660e16f928bf7385390", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:3ad59c84c502cd134b0088ca9038d100e8fb5081bbd5ccca4863f3804d81f61d", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:71afc1f5cd72ab97330126b566bbf4e8661aab7449f08895d21a5d08c6b051ff", + "sha256:7349c27128334f787ae63ab49d90bf6d47c7288c63a0a5dfaa319d4b4541dd2c", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:859714036274a75e6e57c7bab0c47a4602d2a8cfaaa33bbdb68c8359b2ed4f5c", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:869ef4a19f6e4c6987e18b315721b8b971f7048e6eaea29c066854242b4e98d9", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:977e2d9a646773cc7428cdd9a34b069d6ee254fadfb4d09b3f430e95472f3cf3", + "sha256:99bd767c49c775b79fdcd2eabff405f1063d9d959039c0bdd720527a7738748a", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, + "cryptography": { + "hashes": [ + "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", + "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", + "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", + "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", + "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", + "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", + "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", + "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", + "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", + "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", + "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", + "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", + "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", + "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", + "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", + "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", + "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", + "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", + "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" + ], + "index": "pypi", + "version": "==2.5" + }, + "cssselect2": { + "hashes": [ + "sha256:267eebc7378ade2e8be710cd0179606ad9c95ecc673138fccfcfba42c5ce153d", + "sha256:505d2ce3d3a1d390ddb52f7d0864b7efeb115a5b852a91861b498b92424503ab" + ], + "index": "pypi", + "version": "==0.2.1" + }, + "defusedxml": { + "hashes": [ + "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", + "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" + ], + "index": "pypi", + "version": "==0.5.0" + }, + "dominate": { + "hashes": [ + "sha256:4076735c0745fe771e57b2313dbb4bfeec42731816ee23cee509f66e8912aa51", + "sha256:4b9fd42d2824b79761799590697db45bf93daad511b130c50513af38da33df9b" + ], + "index": "pypi", + "version": "==2.3.5" + }, + "elasticsearch": { + "hashes": [ + "sha256:7546cc08e3899716e12fe67d12d7cfe9a64647014d1134b014c3c392b63cad42", + "sha256:aada5cfdc4a543c47098eb3aca6663848ef5d04b4324935ced441debc11ec98b" + ], + "index": "pypi", + "version": "==6.3.1" + }, + "et-xmlfile": { + "hashes": [ + "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b" + ], + "version": "==1.0.1" + }, + "flask": { + "hashes": [ + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "flask-bootstrap": { + "hashes": [ + "sha256:cb08ed940183f6343a64e465e83b3a3f13c53e1baabb8d72b5da4545ef123ac8" + ], + "index": "pypi", + "version": "==3.3.7.1" + }, + "flask-elasticsearch": { + "hashes": [ + "sha256:5f288c275c3865532c6c8af71c1153c05c6d56a45ee1a7faf43dc49acc5f4a2f" + ], + "index": "pypi", + "version": "==0.2.5" + }, + "flask-login": { + "hashes": [ + "sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec" + ], + "index": "pypi", + "version": "==0.4.1" + }, + "flask-mail": { + "hashes": [ + "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41" + ], + "index": "pypi", + "version": "==0.9.1" + }, + "flask-migrate": { + "hashes": [ + "sha256:0c42c34357b24faac63bc6e7af028c8614235f5210eded53802ced450c5392c2", + "sha256:8356fa6a02694da34e78da1e38cf91c944b219f4bd4b89493a3b261a305994ab" + ], + "index": "pypi", + "version": "==2.3.1" + }, + "flask-moment": { + "hashes": [ + "sha256:71a601fcd5be4742227251641cb706c109680b54c5fb25c5d2ed96e576ec3b4d", + "sha256:af7ccd599d85e751ff1f7661904daa51df9950e9bc9bd4ccf174bd38ccbc401f" + ], + "index": "pypi", + "version": "==0.6.0" + }, + "flask-recaptcha": { + "hashes": [ + "sha256:6fe9bc971cc72cbc055b46653a86274dcfc88df4ddce7b6c10d5fbe5dbee0f2e" + ], + "index": "pypi", + "version": "==0.4.2" + }, + "flask-script": { + "hashes": [ + "sha256:6425963d91054cfcc185807141c7314a9c5ad46325911bd24dcb489bd0161c65" + ], + "index": "pypi", + "version": "==2.0.6" + }, + "flask-session": { + "hashes": [ + "sha256:0768e2bbf06f963ec1aa711bde7aa32dc39ff70f89b495d6db687d899eae4423", + "sha256:d75eb27f918421ccaf1ba86353348b84ecf07fc64ce40ff7ad05a190c4bf50f1" + ], + "index": "pypi", + "version": "==0.3.2" + }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b", + "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53" + ], + "index": "pypi", + "version": "==2.3.2" + }, + "flask-tracy": { + "hashes": [ + "sha256:de1c09b81aa0c232ceb1858448239d35a38eeb6b13a86134e1d49ce2c346c15f", + "sha256:f2b0a5b8f2a00db9c05bdc74cd880522822fa4fe016b43a3c73af431b01a7fd9" + ], + "index": "pypi", + "version": "==0.1.3" + }, + "flask-weasyprint": { + "hashes": [ + "sha256:0dc50b3d62da43a2f650e54555713a72651bcf169ff1503282f2003203c854f1" + ], + "index": "pypi", + "version": "==0.5" + }, + "flask-wtf": { + "hashes": [ + "sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", + "sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" + ], + "index": "pypi", + "version": "==0.14.2" + }, + "holidays": { + "hashes": [ + "sha256:b38e11736e2fad04c51e6d8d38ff66b483905d9448ba0291640cda5889687baa", + "sha256:bb91e9502b1621bf5627f04d5f5c82fbf72c492b5bbe36a8fd0c0d463da216da" + ], + "index": "pypi", + "version": "==0.9.9" + }, + "html5lib": { + "hashes": [ + "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", + "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + ], + "index": "pypi", + "version": "==1.0.1" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "index": "pypi", + "version": "==2.8" + }, + "isodate": { + "hashes": [ + "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", + "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81" + ], + "index": "pypi", + "version": "==0.6.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "jdcal": { + "hashes": [ + "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba", + "sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8" + ], + "version": "==1.4.1" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "index": "pypi", + "version": "==2.10" + }, + "jsonschema": { + "hashes": [ + "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", + "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + ], + "index": "pypi", + "version": "==2.6.0" + }, + "kombu": { + "hashes": [ + "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006", + "sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c" + ], + "index": "pypi", + "version": "==5.0.2" + }, + "ldap3": { + "hashes": [ + "sha256:0b70034b0a535b36d1103b37c121f7efa718bb88d0ed9023f121b24b834a9584", + "sha256:3f67c83185b1f0df8fdf6b52fa42c55bc9e9b7120c8b7fec60f0d6003c536d18", + "sha256:6b4139184ef0d160415f94e5cfaa039e5b725ba6986cff49139d35c37d230f00", + "sha256:7a88f3cc2584031e97a2e19f906abb8f655aca80b838d454b1d3c9e103a36c4b", + "sha256:a8cf20c3b51032399cd74a42a0d31dbf0a8bc450598f0972e167ad992a43c9f2", + "sha256:dd9be8ea27773c4ffc18ede0b95c3ca1eb12513a184590b9f8ae423db3f71eb9" + ], + "index": "pypi", + "version": "==2.5.2" + }, + "lxml": { + "hashes": [ + "sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21", + "sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0", + "sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297", + "sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591", + "sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801", + "sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f", + "sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541", + "sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58", + "sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6", + "sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730", + "sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49", + "sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc", + "sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879", + "sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6", + "sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e", + "sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c", + "sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2", + "sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d", + "sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485", + "sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e", + "sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73", + "sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488", + "sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5", + "sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11", + "sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298", + "sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af" + ], + "index": "pypi", + "version": "==4.3.0" + }, + "mako": { + "hashes": [ + "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" + ], + "index": "pypi", + "version": "==1.0.7" + }, + "markupsafe": { + "hashes": [ + "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", + "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", + "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", + "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", + "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", + "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", + "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", + "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", + "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", + "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", + "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", + "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", + "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", + "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", + "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", + "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", + "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", + "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", + "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", + "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", + "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", + "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", + "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", + "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", + "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", + "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", + "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", + "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "oauthlib": { + "hashes": [ + "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", + "sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e" + ], + "index": "pypi", + "version": "==3.0.1" + }, + "odfpy": { + "hashes": [ + "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", + "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0" + ], + "version": "==1.4.1" + }, + "openpyxl": { + "hashes": [ + "sha256:18e11f9a650128a12580a58e3daba14e00a11d9e907c554a17ea016bf1a2c71b", + "sha256:f7d666b569f729257082cf7ddc56262431878f602dcc2bc3980775c59439cdab" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.5" + }, + "paramiko": { + "hashes": [ + "sha256:3c16b2bfb4c0d810b24c40155dbfd113c0521e7e6ee593d704e84b4c658a1f3b", + "sha256:a8975a7df3560c9f1e2b43dc54ebd40fd00a7017392ca5445ce7df409f900fcb" + ], + "index": "pypi", + "version": "==2.4.2" + }, + "pdfrw": { + "hashes": [ + "sha256:0dc0494a0e6561b268542b28ede2280387c2728114f117d3bb5d8e4787b93ef4", + "sha256:758289edaa3b672e9a1a67504be73c18ec668d4e5b9d5ac9cbc0dc753d8d196b" + ], + "index": "pypi", + "version": "==0.4" + }, + "pillow": { + "hashes": [ + "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", + "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", + "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", + "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", + "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", + "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", + "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", + "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", + "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", + "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", + "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", + "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", + "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", + "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", + "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", + "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", + "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", + "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", + "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", + "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", + "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", + "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", + "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", + "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", + "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", + "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", + "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", + "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", + "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", + "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", + "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", + "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", + "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", + "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", + "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", + "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", + "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", + "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", + "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", + "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", + "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", + "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", + "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", + "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", + "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", + "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", + "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", + "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", + "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", + "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", + "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + ], + "index": "pypi", + "version": "==5.4.1" + }, + "pkgconfig": { + "hashes": [ + "sha256:048c3b457da7b6f686b647ab10bf09e2250e4c50acfe6f215398a8b5e6fcdb52", + "sha256:3eb03a6345d4916489d3433f60e6d044a21f50e1d86fb611a52fd28061582065" + ], + "index": "pypi", + "version": "==1.4.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c", + "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==3.0.8" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", + "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", + "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", + "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", + "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", + "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", + "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", + "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", + "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", + "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", + "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", + "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", + "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", + "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", + "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", + "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", + "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", + "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", + "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", + "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", + "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", + "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", + "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", + "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", + "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", + "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", + "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", + "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", + "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", + "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", + "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", + "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", + "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", + "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" + ], + "index": "pypi", + "version": "==2.8.6" + }, + "pyasn1": { + "hashes": [ + "sha256:061442c60842f6d11051d4fdae9bc197b64bd41573a12234a753a0cb80b4f30b", + "sha256:0ee2449bf4c4e535823acc25624c45a8b454f328d59d3f3eeb82d3567100b9bd", + "sha256:5f9fb05c33e53b9a6ee3b1ed1d292043f83df465852bec876e93b47fd2df7eed", + "sha256:65201d28e081f690a32401e6253cca4449ccacc8f3988e811fae66bd822910ee", + "sha256:79b336b073a52fa3c3d8728e78fa56b7d03138ef59f44084de5f39650265b5ff", + "sha256:8ec20f61483764de281e0b4aba7d12716189700debcfa9e7935780850bf527f3", + "sha256:9458d0273f95d035de4c0d5e0643f25daba330582cc71bb554fe6969c015042a", + "sha256:98d97a1833a29ca61cd04a60414def8f02f406d732f9f0bcb49f769faff1b699", + "sha256:b00d7bfb6603517e189d1ad76967c7e805139f63e43096e5f871d1277f50aea5", + "sha256:b06c0cfd708b806ea025426aace45551f91ea7f557e0c2d4fbd9a4b346873ce0", + "sha256:d14d05984581770333731690f5453efd4b82e1e5d824a1d7976b868a2e5c38e8", + "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", + "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + ], + "index": "pypi", + "version": "==0.4.5" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "index": "pypi", + "version": "==2.19" + }, + "pynacl": { + "hashes": [ + "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", + "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", + "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", + "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", + "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", + "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", + "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", + "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", + "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", + "sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5", + "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", + "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", + "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", + "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", + "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", + "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", + "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", + "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", + "sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92", + "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", + "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0" + ], + "index": "pypi", + "version": "==1.3.0" + }, + "pyopenssl": { + "hashes": [ + "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", + "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6" + ], + "index": "pypi", + "version": "==19.0.0" + }, + "pyparsing": { + "hashes": [ + "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", + "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" + ], + "index": "pypi", + "version": "==2.3.1" + }, + "pyphen": { + "hashes": [ + "sha256:3b633a50873156d777e1f1075ba4d8e96a6ad0a3ca42aa3ea9a6259f93f18921", + "sha256:e172faf10992c8c9d369bdc83e36dbcf1121f4ed0d881f1a0b521935aee583b5" + ], + "index": "pypi", + "version": "==0.9.5" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "python-dotenv": { + "hashes": [ + "sha256:a84569d0e00d178bc5b957f7ff208bf49287cbf61857c31c258c4a91f571527b", + "sha256:c9b1ddd3cdbe75c7d462cb84674d87130f4b948f090f02c7d7144779afb99ae0" + ], + "index": "pypi", + "version": "==0.10.1" + }, + "python-editor": { + "hashes": [ + "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", + "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", + "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8", + "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77", + "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522" + ], + "index": "pypi", + "version": "==1.0.4" + }, + "python-magic-bin": { + "hashes": [ + "sha256:34a788c03adde7608028203e2dbb208f1f62225ad91518787ae26d603ae68892", + "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4", + "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69" + ], + "index": "pypi", + "version": "==0.4.14" + }, + "pytz": { + "hashes": [ + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + ], + "index": "pypi", + "version": "==2018.9" + }, + "pyyaml": { + "hashes": [ + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" + ], + "version": "==5.3.1" + }, + "raven": { + "hashes": [ + "sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54", + "sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4" + ], + "index": "pypi", + "version": "==6.10.0" + }, + "redis": { + "hashes": [ + "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", + "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" + ], + "index": "pypi", + "version": "==3.1.0" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", + "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140", + "sha256:dd5a0499abfefd087c6dd96693cbd5bfd28aa009719a7f85ab3fabe3956ef19a" + ], + "index": "pypi", + "version": "==1.2.0" + }, + "simplekv": { + "hashes": [ + "sha256:29c64f4a857f147c2df47281300ba6604f5b5365cd943de32ca5268cc2e5dd68", + "sha256:4a1cfa19a44f51bc71a3b47d32619b801992217f5e12f39c28aaed2dcff5e795", + "sha256:c34608abb2b492c9a06e1c56c6cbb8f88c74ff699490359b9e06874ac58588c2" + ], + "index": "pypi", + "version": "==0.11.11" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "index": "pypi", + "version": "==1.12.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:52a42dbf02d0562d6e90e7af59f177f1cc027e72833cc29c3a821eefa009c71d" + ], + "index": "pypi", + "version": "==1.2.17" + }, + "tablib": { + "hashes": [ + "sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670", + "sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13" + ], + "index": "pypi", + "version": "==0.13.0" + }, + "tinycss2": { + "hashes": [ + "sha256:5e881eaa263bf4dc5c050d43cd6d2203ade1e3a3cda61f5511cf878972e83b78", + "sha256:7c53c2c0e914c7711c295b3101bcc78e0b7eda23ff20228a936efe11cdcc7136" + ], + "index": "pypi", + "version": "==0.6.1" + }, + "tzlocal": { + "hashes": [ + "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e" + ], + "index": "pypi", + "version": "==1.5.1" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "index": "pypi", + "version": "==1.24.1" + }, + "vine": { + "hashes": [ + "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", + "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" + ], + "index": "pypi", + "version": "==5.0.0" + }, + "virtualenv": { + "hashes": [ + "sha256:aabc8ef18cddbd8a2a9c7f92bc43e2fea54b1147330d65db920ef3ce9812e3dc", + "sha256:f74a5943a9005c1e897bcf87f7e8af7e00c06fa9777b9ad691f69f9fa54a87d8" + ], + "index": "pypi", + "version": "==13.1.2" + }, + "visitor": { + "hashes": [ + "sha256:2c737903b2b6864ebc6167eef7cf3b997126f1aa94bdf590f90f1436d23e480a" + ], + "index": "pypi", + "version": "==0.1.3" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + }, + "weasyprint": { + "hashes": [ + "sha256:0265efe2a70cc9c972407f4f868fe59b0876ca361b3da609262f05d3c5156e11", + "sha256:8aefc7704f6a0aa78ffc238d3324f64a7a493c8462ccedb23bf7d0744d9a6ad7" + ], + "index": "pypi", + "version": "==45" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "werkzeug": { + "hashes": [ + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + ], + "index": "pypi", + "version": "==0.14.1" + }, + "wtforms": { + "hashes": [ + "sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", + "sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" + ], + "index": "pypi", + "version": "==2.2.1" + }, + "xlrd": { + "hashes": [ + "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2", + "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.2.0" + }, + "xlwt": { + "hashes": [ + "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", + "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" + ], + "version": "==1.3.0" + }, + "xmlsec": { + "hashes": [ + "sha256:e573c0172174973223d874ffd158ecd4e0faa761015474385289a6468dd29ed6" + ], + "index": "pypi", + "version": "==1.3.3" + } + }, + "develop": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "index": "pypi", + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "index": "pypi", + "version": "==18.2.0" + }, + "coverage": { + "hashes": [ + "sha256:06123b58a1410873e22134ca2d88bd36680479fe354955b3579fb8ff150e4d27", + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:0d34245f824cc3140150ab7848d08b7e2ba67ada959d77619c986f2062e1f0e8", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:258b21c5cafb0c3768861a6df3ab0cfb4d8b495eee5ec660e16f928bf7385390", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:3ad59c84c502cd134b0088ca9038d100e8fb5081bbd5ccca4863f3804d81f61d", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:71afc1f5cd72ab97330126b566bbf4e8661aab7449f08895d21a5d08c6b051ff", + "sha256:7349c27128334f787ae63ab49d90bf6d47c7288c63a0a5dfaa319d4b4541dd2c", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:859714036274a75e6e57c7bab0c47a4602d2a8cfaaa33bbdb68c8359b2ed4f5c", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:869ef4a19f6e4c6987e18b315721b8b971f7048e6eaea29c066854242b4e98d9", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:977e2d9a646773cc7428cdd9a34b069d6ee254fadfb4d09b3f430e95472f3cf3", + "sha256:99bd767c49c775b79fdcd2eabff405f1063d9d959039c0bdd720527a7738748a", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "index": "pypi", + "version": "==4.5.2" + }, + "decorator": { + "hashes": [ + "sha256:73cbaadb8bc4e3c65fe1100773d56331a2d756cc0f5c7b9d8d5d5223fe04f600", + "sha256:953d6bf082b100f43229cf547f4f97f97e970f5ad645ee7601d55ff87afdfe76" + ], + "index": "pypi", + "version": "==4.0.11" + }, + "faker": { + "hashes": [ + "sha256:55f3d00af647a8fc31c7a07b74b7b1d82cb7176fed758e1dc94d3bf0bdb802ba", + "sha256:84e0c48461498dbd5b22bfb2885a200eb93467dc845ba4800354713dde300700" + ], + "index": "pypi", + "version": "==0.7.9" + }, + "ipdb": { + "hashes": [ + "sha256:fffc45b615e46eb75becbd88a30c69c75e7164ecd0122f2c78579b4dfa41b8c9" + ], + "index": "pypi", + "version": "==0.10.2" + }, + "ipython": { + "hashes": [ + "sha256:54d2ec3ba66ae2e0a34b2b48c85c4818d8ca040edd36ffe81f1e460dd5e03ae5", + "sha256:bf5e615e7d96dac5a61fbf98d9e2926d98aa55582681bea7e9382992a3f43c1d", + "sha256:fb342e89069c9ef176063b14888d7fe774253762f1eda19f09332f1440e6f3c7" + ], + "index": "pypi", + "version": "==5.3.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "index": "pypi", + "version": "==0.2.0" + }, + "more-itertools": { + "hashes": [ + "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", + "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", + "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + ], + "index": "pypi", + "version": "==5.0.0" + }, + "pexpect": { + "hashes": [ + "sha256:3d132465a75b57aa818341c6521392a06cc660feb3988d7f1074f39bd23c9a92", + "sha256:f853b52afaf3b064d29854771e2db509ef80392509bde2dd7a6ecf2dfc3f0018" + ], + "index": "pypi", + "version": "==4.2.1" + }, + "pickleshare": { + "hashes": [ + "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", + "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" + ], + "index": "pypi", + "version": "==0.7.4" + }, + "pipdeptree": { + "hashes": [ + "sha256:2a1c0d51069c3a573ea2b00c445c8db8ac7dc13c584d05415d6e8121c808af2d", + "sha256:64ac3e1d6b4d8d96620fc44efd44043eef72bb471cd4433c5e5ad8f3e23d44d5" + ], + "index": "pypi", + "version": "==0.9.0" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", + "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + ], + "index": "pypi", + "version": "==0.6.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c", + "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==3.0.8" + }, + "ptyprocess": { + "hashes": [ + "sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6", + "sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "index": "pypi", + "version": "==1.7.0" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "pytest": { + "hashes": [ + "sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d", + "sha256:c76e93f3145a44812955e8d46cdd302d8a45fbfc7bf22be24fe231f9d8d8853a" + ], + "index": "pypi", + "version": "==3.6.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d", + "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec" + ], + "index": "pypi", + "version": "==2.5.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "index": "pypi", + "version": "==0.8.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "index": "pypi", + "version": "==1.12.0" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "index": "pypi", + "version": "==4.3.2" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + } + } +} diff --git a/app/__init__.py b/app/__init__.py index 0158a8cfd..cdd399b8a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -12,16 +12,14 @@ from flask import (Flask, abort, render_template, request as flask_request) from flask_bootstrap import Bootstrap from flask_elasticsearch import FlaskElasticsearch -from flask_kvsession import KVSessionExtension from flask_login import LoginManager, current_user from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from flask_tracy import Tracy from flask_wtf import CsrfProtect +from flask_session import Session from raven.contrib.flask import Sentry -from simplekv.decorator import PrefixDecorator -from simplekv.memory.redisstore import RedisStore from app import celery_config from app.constants import OPENRECORDS_DL_EMAIL @@ -36,11 +34,12 @@ mail = Mail() tracy = Tracy() login_manager = LoginManager() -store = RedisStore(redis.StrictRedis(db=Config.SESSION_REDIS_DB, - host=Config.REDIS_HOST, port=Config.REDIS_PORT)) -session_redis = PrefixDecorator('session_', store) +store = redis.StrictRedis(db=Config.SESSION_REDIS_DB, + host=Config.REDIS_HOST, + port=Config.REDIS_PORT) celery = Celery(__name__, broker=Config.CELERY_BROKER_URL) sentry = Sentry() +sess = Session() upload_redis = redis.StrictRedis( db=Config.UPLOAD_REDIS_DB, host=Config.REDIS_HOST, port=Config.REDIS_PORT) @@ -113,6 +112,7 @@ def create_app(config_name='default'): celery.conf.update(app.config) celery.config_from_object(celery_config) sentry.init_app(app, logging=app.config["USE_SENTRY"], level=logging.INFO) + sess.init_app(app) with app.app_context(): from app.models import Anonymous @@ -121,7 +121,6 @@ def create_app(config_name='default'): if app.config['USE_SAML']: login_manager.login_message = None login_manager.login_message_category = None - KVSessionExtension(session_redis, app) # Error Handlers @app.errorhandler(400) diff --git a/app/auth/utils.py b/app/auth/utils.py index 33899def9..e95700503 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -407,7 +407,7 @@ def saml_sls(saml_sp, user_guid): Response Object: Redirect the user to the Home Page. """ - dscb = session.destroy() + dscb = session.clear() url = saml_sp.process_slo(delete_session_cb=dscb) errors = saml_sp.get_errors() logout_user() diff --git a/app/auth/views.py b/app/auth/views.py index fa16c413b..ed2b1834f 100644 --- a/app/auth/views.py +++ b/app/auth/views.py @@ -116,7 +116,7 @@ def logout(): elif current_app.config["USE_LOCAL_AUTH"]: logout_user() - session.destroy() + session.clear() if timeout: flash("Your session timed out. Please login again", category="info") return redirect(url_for("main.index")) @@ -259,7 +259,6 @@ def ldap_login(): if authenticated: login_user(user) - session.regenerate() session["user_id"] = current_user.get_id() create_auth_event( @@ -332,7 +331,7 @@ def ldap_logout(): 'timed_out': timed_out } ) - session.destroy() + session.clear() if timed_out: flash("Your session timed out. Please login again", category="info") return redirect(url_for("main.index")) @@ -360,7 +359,6 @@ def local_login(): if user is not None: login_user(user) - session.regenerate() session["user_id"] = current_user.get_id() create_auth_event( @@ -411,7 +409,7 @@ def local_logout(timed_out=False): 'type': current_app.config['AUTH_TYPE'] } ) - session.destroy() + session.clear() if timed_out: flash("Your session timed out. Please login again", category="info") return redirect(url_for("main.index")) diff --git a/app/celery_config.py b/app/celery_config.py index 3f5ac31ff..c679eed30 100644 --- a/app/celery_config.py +++ b/app/celery_config.py @@ -18,5 +18,10 @@ 'update_next_request_number': { 'task': 'app.jobs.update_next_request_number', 'schedule': crontab(minute='0', hour='0', day_of_month='1', month_of_year='1') + }, + # Every 30 minutes + 'clear_expired_session_ids': { + 'task': 'app.jobs.clear_expired_session_ids', + 'schedule': crontab(minute='30') } } diff --git a/app/jobs.py b/app/jobs.py index 787f2ca01..7bd07407a 100644 --- a/app/jobs.py +++ b/app/jobs.py @@ -1,18 +1,19 @@ import traceback from datetime import datetime +# import celery +from celery import Celery from flask import (current_app, render_template) -import celery from psycopg2 import OperationalError from sqlalchemy.exc import SQLAlchemyError -from app import calendar, sentry, db +from app import calendar, db, store from app.constants import OPENRECORDS_DL_EMAIL, request_status from app.constants.event_type import EMAIL_NOTIFICATION_SENT, REQ_STATUS_CHANGED from app.constants.response_privacy import PRIVATE from app.lib.db_utils import create_object, update_object from app.lib.email_utils import send_email -from app.models import Agencies, Emails, Events, Requests +from app.models import Agencies, Emails, Events, Requests, Users # NOTE: (For Future Reference) # If we find ourselves in need of a request context, app.test_request_context() might come in handy. @@ -20,8 +21,10 @@ STATUSES_EMAIL_SUBJECT = "Nightly Request Status Report" STATUSES_EMAIL_TEMPLATE = "email_templates/email_request_status_changed" +app = Celery() -@celery.task(autoretry_for=(OperationalError, SQLAlchemyError,), retry_kwargs={'max_retries': 5}, retry_backoff=True) + +@app.task(autoretry_for=(OperationalError, SQLAlchemyError,), retry_kwargs={'max_retries': 5}, retry_backoff=True) def update_request_statuses(): try: _update_request_statuses() @@ -161,7 +164,7 @@ def _update_request_statuses(): ) -@celery.task(autoretry_for=(OperationalError, SQLAlchemyError,), retry_kwargs={'max_retries': 5}, retry_backoff=True) +@app.task(autoretry_for=(OperationalError, SQLAlchemyError,), retry_kwargs={'max_retries': 5}, retry_backoff=True) def update_next_request_number(): """ Celery task to automatically update the next request number of each agency to 1 @@ -174,3 +177,22 @@ def update_next_request_number(): db.session.commit() except SQLAlchemyError: db.session.rollback() + + +@app.task(autoretry_for=(OperationalError, SQLAlchemyError,), retry_kwargs={'max_retries': 5}, retry_backoff=True) +def clear_expired_session_ids(): + """ + Celery task to clear session ids that are no longer valid. + :return: + """ + users = Users.query.with_entities(Users.guid, Users.session_id).filter(Users.session_id.isnot(None)).all() + if users: + for user in users: + if store.get("session:" + user[1]) is None: + update_object( + { + 'session_id': None + }, + Users, + user[0] + ) diff --git a/app/lib/redis_utils.py b/app/lib/redis_utils.py index 15a5a1b4f..ca655e49e 100644 --- a/app/lib/redis_utils.py +++ b/app/lib/redis_utils.py @@ -1,20 +1,15 @@ import os -from app import sentry try: import cPickle as pickle except ImportError: import pickle -from flask import current_app from app import upload_redis as redis from app.lib.file_utils import ( os_get_hash, os_get_mime_type ) -from flask_kvsession import ( - KVSession -) # Redis File Utilities @@ -58,25 +53,3 @@ def _get_file_metadata_key(request_or_response_id, filepath, is_update): return '|'.join((str(request_or_response_id), os.path.basename(filepath), 'update' if is_update else 'new')) - - -# Redis Session Utilities -def redis_get_user_session(session_id): - serialization_method = pickle - session_class = KVSession - - try: - s = session_class(serialization_method.loads( - current_app.kvsession_store.get(session_id) - )) - s.sid_s = session_id - - return s - - except KeyError: - sentry.captureException() - return None - - -def redis_delete_user_session(session_id): - redis.delete(session_id) diff --git a/app/main/views.py b/app/main/views.py index 9d2230b7c..f75d2da4a 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -5,7 +5,6 @@ """ from flask import ( current_app, - render_template, flash, render_template, request, @@ -30,7 +29,7 @@ def index(): return render_template('main/home.html', duplicate_session=True) update_object( { - 'session_id': session.sid_s + 'session_id': session.sid }, Users, current_user.guid diff --git a/app/static/js/plugins/bootstrap-session-timeout.min.js b/app/static/js/plugins/bootstrap-session-timeout.min.js deleted file mode 100755 index 5c4792208..000000000 --- a/app/static/js/plugins/bootstrap-session-timeout.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(a){"use strict";a.sessionTimeout=function(b){function c(){n||(a.ajax({type:i.ajaxType,url:i.keepAliveUrl,data:i.ajaxData}),n=!0,setTimeout(function(){n=!1},i.keepAliveInterval))}function d(){clearTimeout(g),(i.countdownMessage||i.countdownBar)&&f("session",!0),"function"==typeof i.onStart&&i.onStart(i),i.keepAlive&&c(),g=setTimeout(function(){"function"!=typeof i.onWarn?a("#session-timeout-dialog").modal("show"):i.onWarn(i),e()},i.warnAfter)}function e(){clearTimeout(g),a("#session-timeout-dialog").hasClass("in")||!i.countdownMessage&&!i.countdownBar||f("dialog",!0),g=setTimeout(function(){"function"!=typeof i.onRedir?window.location=i.redirUrl:i.onRedir(i)},i.redirAfter-i.warnAfter)}function f(b,c){clearTimeout(j.timer),"dialog"===b&&c?j.timeLeft=Math.floor((i.redirAfter-i.warnAfter)/1e3):"session"===b&&c&&(j.timeLeft=Math.floor(i.redirAfter/1e3)),i.countdownBar&&"dialog"===b?j.percentLeft=Math.floor(j.timeLeft/((i.redirAfter-i.warnAfter)/1e3)*100):i.countdownBar&&"session"===b&&(j.percentLeft=Math.floor(j.timeLeft/(i.redirAfter/1e3)*100));var d=a(".countdown-holder"),e=j.timeLeft>=0?j.timeLeft:0;if(i.countdownSmart){var g=Math.floor(e/60),h=e%60,k=g>0?g+"m":"";k.length>0&&(k+=" "),k+=h+"s",d.text(k)}else d.text(e+"s");i.countdownBar&&a(".countdown-bar").css("width",j.percentLeft+"%"),j.timeLeft=j.timeLeft-1,j.timer=setTimeout(function(){f(b)},1e3)}var g,h={title:"Your Session is About to Expire!",message:"Your session is about to expire.",logoutButton:"Logout",keepAliveButton:"Stay Connected",keepAliveUrl:"/keep-alive",ajaxType:"POST",ajaxData:"",redirUrl:"/timed-out",logoutUrl:"/log-out",warnAfter:9e5,redirAfter:12e5,keepAliveInterval:5e3,keepAlive:!0,ignoreUserActivity:!1,onStart:!1,onWarn:!1,onRedir:!1,countdownMessage:!1,countdownBar:!1,countdownSmart:!1},i=h,j={};if(b&&(i=a.extend(h,b)),i.warnAfter>=i.redirAfter)return console.error('Bootstrap-session-timeout plugin is miss-configured. Option "redirAfter" must be equal or greater than "warnAfter".'),!1;if("function"!=typeof i.onWarn){var k=i.countdownMessage?"

"+i.countdownMessage.replace(/{timer}/g,'')+"

":"",l=i.countdownBar?'
':"";a("body").append('"),a("#session-timeout-dialog-logout").on("click",function(){window.location=i.logoutUrl}),a("#session-timeout-dialog").on("hide.bs.modal",function(){d()})}if(!i.ignoreUserActivity){var m=[-1,-1];a(document).on("keyup mouseup mousemove touchend touchmove",function(b){if("mousemove"===b.type){if(b.clientX===m[0]&&b.clientY===m[1])return;m[0]=b.clientX,m[1]=b.clientY}d(),a("#session-timeout-dialog").length>0&&a("#session-timeout-dialog").data("bs.modal")&&a("#session-timeout-dialog").data("bs.modal").isShown&&(a("#session-timeout-dialog").modal("hide"),a("body").removeClass("modal-open"),a("div.modal-backdrop").remove())})}var n=!1;d()}}(jQuery); \ No newline at end of file diff --git a/app/static/js/plugins/session-timeout.js b/app/static/js/plugins/session-timeout.js new file mode 100644 index 000000000..58dca470c --- /dev/null +++ b/app/static/js/plugins/session-timeout.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.sessionTimeout=t():e.sessionTimeout=t()}(window,(function(){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);n(1);t.default=function(e){var t,n,o=Object.assign({appendTimestamp:!1,keepAliveMethod:"POST",keepAliveUrl:"/keep-alive",logOutBtnText:"Log out now",logOutUrl:"/log-out",message:"Your session is about to expire.",stayConnectedBtnText:"Stay connected",timeOutAfter:12e5,timeOutUrl:"/timed-out",titleText:"Session Timetout",warnAfter:9e5},e),r=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),a=document.createElement("div"),u=document.createElement("div"),c=document.createElement("button"),l=document.createElement("button"),d=function(){r.classList.remove("sessionTimeout--hidden"),clearTimeout(t)},f=function(){window.location=o.timeOutUrl};c.addEventListener("click",(function(){window.location=o.logOutUrl})),l.addEventListener("click",(function(){r.classList.add("sessionTimeout--hidden");var e=o.appendTimestamp?"".concat(o.keepAliveUrl,"?time=").concat(Date.now()):o.keepAliveUrl,i=new XMLHttpRequest;i.open(o.keepAliveMethod,e),i.send(),t=setTimeout(d,o.warnAfter),clearTimeout(n),n=setTimeout(f,o.timeOutAfter)})),r.classList.add("sessionTimeout","sessionTimeout--hidden"),i.classList.add("sessionTimeout-modal"),a.classList.add("sessionTimeout-title"),s.classList.add("sessionTimeout-content"),u.classList.add("sessionTimeout-buttons"),c.classList.add("sessionTimeout-btn","sessionTimeout-btn--secondary"),l.classList.add("sessionTimeout-btn","sessionTimeout-btn--primary"),a.innerText=o.titleText,s.innerText=o.message,c.innerText=o.logOutBtnText,l.innerText=o.stayConnectedBtnText,i.appendChild(a),i.appendChild(s),i.appendChild(u),u.appendChild(c),u.appendChild(l),r.appendChild(i),document.body.appendChild(r),t=setTimeout(d,o.warnAfter),n=setTimeout(f,o.timeOutAfter)}},function(e,t,n){var o=n(2);"string"==typeof o&&(o=[[e.i,o,""]]);var r={hmr:!0,transform:void 0,insertInto:void 0};n(4)(o,r);o.locals&&(e.exports=o.locals)},function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".sessionTimeout {\n position: fixed;\n z-index: 1;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: #D8D8D8;\n background-color: rgba(0, 0, 0, 0.5);\n}\n\n.sessionTimeout-modal {\n background-color: #FFFFFF;\n margin: 10% auto;\n padding: 0.2rem;\n width: 50%;\n}\n\n.sessionTimeout-title {\n font-family: Helvetica, Arial, sans-serif;\n background-color: #DEDEDE;\n font-weight: bold;\n padding: .4em 1em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sessionTimeout-content {\n font-size: 22px;\n font-family: Helvetica, Arial, sans-serif;\n text-align: center;\n margin: 1em 0 2em 0;\n}\n\n.sessionTimeout-buttons {\n text-align: right;\n}\n\n.sessionTimeout-btn {\n font-size: 16px;\n border: none;\n padding: 0.5em 0.75em;\n margin: 0 0.25em;\n background-color: #6C757D;\n color: #FFFFFF;\n cursor: pointer;\n}\n\n.sessionTimeout-btn:hover {\n background-color: #5A6268;\n}\n\n.sessionTimeout-btn--primary {\n background-color: #007BFF;\n}\n\n.sessionTimeout-btn--primary:hover {\n background-color: #0069D9;\n}\n\n.sessionTimeout--hidden {\n display: none;\n}\n",""])},function(e,t,n){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",o=e[3];if(!o)return n;if(t&&"function"==typeof btoa){var r=(s=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(s))))+" */"),i=o.sources.map((function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"}));return[n].concat(i).concat([r]).join("\n")}var s;return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n})).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var o={},r=0;r=0&&d.splice(t,1)}function b(e){var t=document.createElement("style");if(void 0===e.attrs.type&&(e.attrs.type="text/css"),void 0===e.attrs.nonce){var o=function(){0;return n.nc}();o&&(e.attrs.nonce=o)}return y(t,e.attrs),v(e,t),t}function y(e,t){Object.keys(t).forEach((function(n){e.setAttribute(n,t[n])}))}function g(e,t){var n,o,r,i;if(t.transform&&e.css){if(!(i="function"==typeof t.transform?t.transform(e.css):t.transform.default(e.css)))return function(){};e.css=i}if(t.singleton){var s=l++;n=c||(c=b(t)),o=x.bind(null,n,s,!1),r=x.bind(null,n,s,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",y(t,e.attrs),v(e,t),t}(t),o=L.bind(null,n,t),r=function(){h(n),n.href&&URL.revokeObjectURL(n.href)}):(n=b(t),o=O.bind(null,n),r=function(){h(n)});return o(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;o(e=t)}else r()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=s()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=m(e,t);return p(n,t),function(e){for(var o=[],r=0;r - + {% include 'base.js.html' %} {% block custom_script %} {% endblock %} diff --git a/app/templates/base.js.html b/app/templates/base.js.html index c9c83dfe1..efee4e63d 100644 --- a/app/templates/base.js.html +++ b/app/templates/base.js.html @@ -25,27 +25,16 @@ } }); {% if current_user.is_authenticated %} - $.sessionTimeout({ - title: 'Your Session is About to Expire!', - message: 'Your session is about to expire.', - logoutButton: 'Logout', - keepAliveButton: 'Stay Connected', - keepAliveUrl: '/active', - ajaxType: 'POST', - ajaxData: '', - redirUrl: '/auth/logout?timeout=True&forced_logout=True', - logoutUrl: '/auth/logout', + sessionTimeout({ warnAfter: 10 * 60 * 1000, // 10 minutes - redirAfter: 15 * 60 * 1000, // 15 minutes - keepAliveInterval: 1 * 60 * 1000, // 1 minute - keepAlive: true, - ignoreUserActivity: false, - onStart: false, - onWarn: false, - onRedir: false, - countdownMessage: 'You will be automatically logged out in {timer}.', - countdownBar: true, - countdownSmart: true + timeOutAfter: 15 * 60 * 1000, // 15 minutes + timeOutUrl: '/auth/logout?timeout=True', + logOutUrl: '/auth/logout', + keepAliveUrl: '/active', + titleText: 'Your Session is About to Expire!', + message: 'Your session is about to expire.', + stayConnectedBtnText: 'Stay Connected', + logoutBtnText: 'Logout', }); {% if duplicate_session %} $("#concurrent-session-modal").modal("show"); diff --git a/config.py b/config.py index d18f4d2e1..5b309200c 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,6 @@ from datetime import timedelta, datetime -import os +import os, redis from dotenv import load_dotenv from app.constants import OPENRECORDS_DL_EMAIL @@ -59,6 +59,7 @@ class Config: # Authentication Settings PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 30))) + SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" AUTH_TYPE = 'None' @@ -95,6 +96,10 @@ class Config: UPLOAD_REDIS_DB = 2 EMAIL_REDIS_DB = 3 + SESSION_REDIS = redis.StrictRedis(db=SESSION_REDIS_DB, + host=REDIS_HOST, + port=REDIS_PORT) + # Celery Settings CELERY_BROKER_URL = 'redis://{redis_host}:{redis_port}/{celery_redis_db}'.format( redis_host=REDIS_HOST, From 453626bc4b4913b3bf73bb52a10f1231e0f0c9b7 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 20 Nov 2020 11:38:21 -0500 Subject: [PATCH 02/44] Add Clear-Site-Data header on logout --- app/auth/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/auth/utils.py b/app/auth/utils.py index e95700503..c8a0c4bf8 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -420,7 +420,9 @@ def saml_sls(saml_sp, user_guid): 'type': current_app.config['AUTH_TYPE'] } ) - return redirect(url) if url else redirect(url_for('main.index')) + redir = redirect(url) if url else redirect(url_for('main.index')) + redir.headers['Clear-Site-Data'] = '"*"' + return redir else: error_message = "Errors on SAML Logout:\n{errors}".format(errors='\n'.join(errors)) current_app.logger.exception(error_message) @@ -433,8 +435,10 @@ def saml_sls(saml_sp, user_guid): 'errors': error_message } ) + redir = redirect(url_for('main.index')) + redir.headers['Clear-Site-Data'] = '"*"' flash("Sorry! An unexpected error has occurred. Please try again later.", category='danger') - return redirect(url_for('main.index')) + return redir def saml_slo(saml_sp): From 7ab2419f9fa4ed8c590f373db73b9d130925363d Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Mon, 7 Dec 2020 17:09:34 -0500 Subject: [PATCH 03/44] Install pyotp and qrious; add MFA table; WIP views --- Pipfile | 1 + Pipfile.lock | 31 ++++++++++++++---- app/main/views.py | 11 ++++++- app/models.py | 22 +++++++++++++ app/static/js/plugins/qrious.min.js | 6 ++++ app/templates/main/qr.html | 32 +++++++++++++++++++ app/templates/main/verify_mfa.html | 18 +++++++++++ ..._user_requests_events_on_update_cascade.py | 8 ++--- 8 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 app/static/js/plugins/qrious.min.js create mode 100644 app/templates/main/qr.html create mode 100644 app/templates/main/verify_mfa.html diff --git a/Pipfile b/Pipfile index 7cbe4b951..9aca8f5a6 100644 --- a/Pipfile +++ b/Pipfile @@ -112,6 +112,7 @@ billiard = "*" kombu = "*" vine = "*" amqp = "*" +pyotp = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 2995aa757..386599c70 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c985a47a5c3cdc822ec0b89b975ccafdc418ea906740f127dcd73607667865fc" + "sha256": "66a3c54044861c8659527d586b8ad86596daddaa22d1190356155cc101658da0" }, "pipfile-spec": 6, "requires": { @@ -141,11 +141,11 @@ }, "celery": { "hashes": [ - "sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf", - "sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6" + "sha256:2cc87518fdd41375c8f40e2b0b59391568a3fb0a0b5f859f7bedef9b37efbc90", + "sha256:d7bcb0fca6fc94c41c946e8979c4c276a02cc7532c68757b43b25c2fa9eda68b" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.0.3" }, "certifi": { "hashes": [ @@ -215,6 +215,13 @@ ], "version": "==0.0.3" }, + "click-plugins": { + "hashes": [ + "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", + "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8" + ], + "version": "==1.1.1" + }, "click-repl": { "hashes": [ "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5", @@ -807,6 +814,14 @@ "index": "pypi", "version": "==19.0.0" }, + "pyotp": { + "hashes": [ + "sha256:038a3f70b34eaad3f72459e8b411662ef8dfcdd95f7d9203fa489e987a75584b", + "sha256:ef07c393660529261e66902e788b32e46260d2c29eb740978df778260a1c2b4c" + ], + "index": "pypi", + "version": "==2.4.1" + }, "pyparsing": { "hashes": [ "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", @@ -872,11 +887,13 @@ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" @@ -1059,11 +1076,11 @@ "develop": { "appnope": { "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442", + "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a" ], "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" + "version": "==0.1.2" }, "atomicwrites": { "hashes": [ diff --git a/app/main/views.py b/app/main/views.py index f75d2da4a..a4b78e770 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -10,8 +10,9 @@ request, session ) -from flask_login import current_user +from flask_login import current_user, login_required +import pyotp from app.constants import OPENRECORDS_DL_EMAIL from app.constants.response_privacy import PRIVATE from app.lib.db_utils import create_object, update_object @@ -21,6 +22,14 @@ from . import main +@main.route('/mfa', methods=['GET', 'POST']) +@login_required +def mfa(): + secret = pyotp.random_base32() + qr_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=current_user.email, issuer_name='OpenRecords') + return render_template('main/qr.html', qr_uri=qr_uri) + + @main.route('/', methods=['GET', 'POST']) def index(): fresh_login = request.args.get('fresh_login', False) diff --git a/app/models.py b/app/models.py index 60592fad3..85a4c55db 100644 --- a/app/models.py +++ b/app/models.py @@ -2035,3 +2035,25 @@ def populate(cls, json_name=None): ) db.session.add(custom_request_form) db.session.commit() + + +class MFA(db.Model): + __tablename__ = "mfa" + user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) + secret = db.Column(db.String(32), nullable=False) + device_name = db.Column(db.String(32), nullable=False) + is_valid = db.Column(db.Boolean(), nullable=False, default=False) + + __table_args__ = ( + db.ForeignKeyConstraint([user_guid], [Users.guid], onupdate="CASCADE"), + ) + + def __init__(self, + user_guid, + secret, + device_name, + is_valid): + self.user_guid = user_guid + self.secret = secret + self.device_name = device_name + self.is_valid = is_valid diff --git a/app/static/js/plugins/qrious.min.js b/app/static/js/plugins/qrious.min.js new file mode 100644 index 000000000..5735ea62d --- /dev/null +++ b/app/static/js/plugins/qrious.min.js @@ -0,0 +1,6 @@ +/*! QRious v4.0.2 | (C) 2017 Alasdair Mercer | GPL v3 License +Based on jsqrencode | (C) 2010 tz@execpc.com | GPL v3 License +*/ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.QRious=e()}(this,function(){"use strict";function t(t,e){var n;return"function"==typeof Object.create?n=Object.create(t):(s.prototype=t,n=new s,s.prototype=null),e&&i(!0,n,e),n}function e(e,n,s,r){var o=this;return"string"!=typeof e&&(r=s,s=n,n=e,e=null),"function"!=typeof n&&(r=s,s=n,n=function(){return o.apply(this,arguments)}),i(!1,n,o,r),n.prototype=t(o.prototype,s),n.prototype.constructor=n,n.class_=e||o.class_,n.super_=o,n}function i(t,e,i){for(var n,s,a=0,h=(i=o.call(arguments,2)).length;a>1&1,n=0;n0;e--)n[e]=n[e]?n[e-1]^_.EXPONENT[v._modN(_.LOG[n[e]]+t)]:n[e-1];n[0]=_.EXPONENT[v._modN(_.LOG[n[0]]+t)]}for(t=0;t<=i;t++)n[t]=_.LOG[n[t]]},_checkBadness:function(){var t,e,i,n,s,r=0,o=this._badness,a=this.buffer,h=this.width;for(s=0;sh*h;)u-=h*h,c++;for(r+=c*v.N4,n=0;n=o-2&&(t=o-2,s>9&&t--);var a=t;if(s>9){for(r[a+2]=0,r[a+3]=0;a--;)e=r[a],r[a+3]|=255&e<<4,r[a+2]=e>>4;r[2]|=255&t<<4,r[1]=t>>4,r[0]=64|t>>12}else{for(r[a+1]=0,r[a+2]=0;a--;)e=r[a],r[a+2]|=255&e<<4,r[a+1]=e>>4;r[1]|=255&t<<4,r[0]=64|t>>4}for(a=t+3-(s<10);a=5&&(i+=v.N1+n[e]-5);for(e=3;et||3*n[e-3]>=4*n[e]||3*n[e+3]>=4*n[e])&&(i+=v.N3);return i},_finish:function(){this._stringBuffer=this.buffer.slice();var t,e,i=0,n=3e4;for(e=0;e<8&&(this._applyMask(e),(t=this._checkBadness())>=1)1&n&&(s[r-1-e+8*r]=1,e<6?s[8+r*e]=1:s[8+r*(e+1)]=1);for(e=0;e<7;e++,n>>=1)1&n&&(s[8+r*(r-7+e)]=1,e?s[6-e+8*r]=1:s[7+8*r]=1)},_interleaveBlocks:function(){var t,e,i=this._dataBlock,n=this._ecc,s=this._eccBlock,r=0,o=this._calculateMaxLength(),a=this._neccBlock1,h=this._neccBlock2,f=this._stringBuffer;for(t=0;t1)for(t=u.BLOCK[n],i=s-7;;){for(e=s-7;e>t-3&&(this._addAlignment(e,i),!(e6)for(t=d.BLOCK[r-7],e=17,i=0;i<6;i++)for(n=0;n<3;n++,e--)1&(e>11?r>>e-12:t>>e)?(s[5-i+o*(2-n+o-11)]=1,s[2-n+o-11+o*(5-i)]=1):(this._setMask(5-i,2-n+o-11),this._setMask(2-n+o-11,5-i))},_isMasked:function(t,e){var i=v._getMaskBit(t,e);return 1===this._mask[i]},_pack:function(){var t,e,i,n=1,s=1,r=this.width,o=r-1,a=r-1,h=(this._dataBlock+this._eccBlock)*(this._neccBlock1+this._neccBlock2)+this._neccBlock2;for(e=0;ee&&(i=t,t=e,e=i),i=e,i+=e*e,i>>=1,i+=t},_modN:function(t){for(;t>=255;)t=((t-=255)>>8)+(255&t);return t},N1:3,N2:3,N3:40,N4:10}),p=v,m=f.extend({draw:function(){this.element.src=this.qrious.toDataURL()},reset:function(){this.element.src=""},resize:function(){var t=this.element;t.width=t.height=this.qrious.size}}),g=h.extend(function(t,e,i,n){this.name=t,this.modifiable=Boolean(e),this.defaultValue=i,this._valueTransformer=n},{transform:function(t){var e=this._valueTransformer;return"function"==typeof e?e(t,this):t}}),k=h.extend(null,{abs:function(t){return null!=t?Math.abs(t):null},hasOwn:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},noop:function(){},toUpperCase:function(t){return null!=t?t.toUpperCase():null}}),w=h.extend(function(t){this.options={},t.forEach(function(t){this.options[t.name]=t},this)},{exists:function(t){return null!=this.options[t]},get:function(t,e){return w._get(this.options[t],e)},getAll:function(t){var e,i=this.options,n={};for(e in i)k.hasOwn(i,e)&&(n[e]=w._get(i[e],t));return n},init:function(t,e,i){"function"!=typeof i&&(i=k.noop);var n,s;for(n in this.options)k.hasOwn(this.options,n)&&(s=this.options[n],w._set(s,s.defaultValue,e),w._createAccessor(s,e,i));this._setAll(t,e,!0)},set:function(t,e,i){return this._set(t,e,i)},setAll:function(t,e){return this._setAll(t,e)},_set:function(t,e,i,n){var s=this.options[t];if(!s)throw new Error("Invalid option: "+t);if(!s.modifiable&&!n)throw new Error("Option cannot be modified: "+t);return w._set(s,e,i)},_setAll:function(t,e,i){if(!t)return!1;var n,s=!1;for(n in t)k.hasOwn(t,n)&&this._set(n,t[n],e,i)&&(s=!0);return s}},{_createAccessor:function(t,e,i){var n={get:function(){return w._get(t,e)}};t.modifiable&&(n.set=function(n){w._set(t,n,e)&&i(n,t)}),Object.defineProperty(e,t.name,n)},_get:function(t,e){return e["_"+t.name]},_set:function(t,e,i){var n="_"+t.name,s=i[n],r=t.transform(null!=e?e:t.defaultValue);return i[n]=r,r!==s}}),M=w,b=h.extend(function(){this._services={}},{getService:function(t){var e=this._services[t];if(!e)throw new Error("Service is not being managed with name: "+t);return e},setService:function(t,e){if(this._services[t])throw new Error("Service is already managed with name: "+t);e&&(this._services[t]=e)}}),B=new M([new g("background",!0,"white"),new g("backgroundAlpha",!0,1,k.abs),new g("element"),new g("foreground",!0,"black"),new g("foregroundAlpha",!0,1,k.abs),new g("level",!0,"L",k.toUpperCase),new g("mime",!0,"image/png"),new g("padding",!0,null,k.abs),new g("size",!0,100,k.abs),new g("value",!0,"")]),y=new b,O=h.extend(function(t){B.init(t,this,this.update.bind(this));var e=B.get("element",this),i=y.getService("element"),n=e&&i.isCanvas(e)?e:i.createCanvas(),s=e&&i.isImage(e)?e:i.createImage();this._canvasRenderer=new c(this,n,!0),this._imageRenderer=new m(this,s,s===e),this.update()},{get:function(){return B.getAll(this)},set:function(t){B.setAll(t,this)&&this.update()},toDataURL:function(t){return this.canvas.toDataURL(t||this.mime)},update:function(){var t=new p({level:this.level,value:this.value});this._canvasRenderer.render(t),this._imageRenderer.render(t)}},{use:function(t){y.setService(t.getName(),t)}});Object.defineProperties(O.prototype,{canvas:{get:function(){return this._canvasRenderer.getElement()}},image:{get:function(){return this._imageRenderer.getElement()}}});var A=O,L=h.extend({getName:function(){}}).extend({createCanvas:function(){},createImage:function(){},getName:function(){return"element"},isCanvas:function(t){},isImage:function(t){}}).extend({createCanvas:function(){return document.createElement("canvas")},createImage:function(){return document.createElement("img")},isCanvas:function(t){return t instanceof HTMLCanvasElement},isImage:function(t){return t instanceof HTMLImageElement}});return A.use(new L),A}); + +//# sourceMappingURL=qrious.min.js.map \ No newline at end of file diff --git a/app/templates/main/qr.html b/app/templates/main/qr.html new file mode 100644 index 000000000..a4be4a2e7 --- /dev/null +++ b/app/templates/main/qr.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}MFA Setup{% endblock %} + +{% block content %} +
+
+

Register MFA Device

+ +
+
+ + + +
+ +
+
+{% endblock %} + +{% block custom_script %} + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/main/verify_mfa.html b/app/templates/main/verify_mfa.html new file mode 100644 index 000000000..5c65618b4 --- /dev/null +++ b/app/templates/main/verify_mfa.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Verify MFA{% endblock %} + +{% block content %} +
+
+ + + +
+ +
+
+{% endblock %} + +{% block custom_script %} +{% endblock %} \ No newline at end of file diff --git a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py index f8cd9de5c..7d2b5cd87 100644 --- a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py +++ b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py @@ -16,7 +16,7 @@ def upgrade(): ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") + # op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") op.create_foreign_key( None, "events", @@ -25,9 +25,9 @@ def upgrade(): ["guid", "auth_user_type"], onupdate="CASCADE", ) - op.drop_constraint( - "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" - ) + #op.drop_constraint( + # "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" + #) op.create_foreign_key( None, "user_requests", From 9dbc9570c4eb463a2376b6eb30d0630b115bf3a9 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 23 Dec 2020 16:58:08 -0500 Subject: [PATCH 04/44] Create MFA blueprint and implement MFA backend functionality --- app/__init__.py | 11 +- app/auth/utils.py | 4 +- app/constants/event_type.py | 2 + app/main/views.py | 42 +++--- app/mfa/__init__.py | 5 + app/mfa/forms.py | 29 +++++ app/mfa/views.py | 121 ++++++++++++++++++ app/models.py | 18 ++- app/static/styles/mfa.css | 13 ++ app/templates/main/verify_mfa.html | 18 --- app/templates/mfa/manage.html | 36 ++++++ .../{main/qr.html => mfa/register.html} | 8 +- app/templates/mfa/verify.html | 18 +++ config.py | 1 + migrations/versions/d97f97be6905_add_mfa.py | 38 ++++++ 15 files changed, 317 insertions(+), 47 deletions(-) create mode 100644 app/mfa/__init__.py create mode 100644 app/mfa/forms.py create mode 100644 app/mfa/views.py create mode 100644 app/static/styles/mfa.css delete mode 100644 app/templates/main/verify_mfa.html create mode 100644 app/templates/mfa/manage.html rename app/templates/{main/qr.html => mfa/register.html} (69%) create mode 100644 app/templates/mfa/verify.html create mode 100644 migrations/versions/d97f97be6905_add_mfa.py diff --git a/app/__init__.py b/app/__init__.py index cdd399b8a..7c0d23b9f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,7 +9,7 @@ import redis from business_calendar import Calendar, MO, TU, WE, TH, FR from celery import Celery -from flask import (Flask, abort, render_template, request as flask_request) +from flask import (Flask, abort, redirect, render_template, request as flask_request, session, url_for) from flask_bootstrap import Bootstrap from flask_elasticsearch import FlaskElasticsearch from flask_login import LoginManager, current_user @@ -175,6 +175,12 @@ def check_maintenance_mode(): if not flask_request.cookies.get('authorized_maintainer', None): return abort(503) + @app.before_request + def check_valid_login(): + if current_user.is_authenticated: + if not session.get('mfa_verified', False) and flask_request.endpoint not in ['mfa.register', 'mfa.verify', 'static', 'auth.logout']: + return redirect(url_for('mfa.verify')) + @app.context_processor def add_session_config(): """Add current_app.permanent_session_lifetime converted to milliseconds @@ -236,4 +242,7 @@ def add_debug(): from .permissions import permissions app.register_blueprint(permissions, url_prefix="/permissions/api/v1.0") + from .mfa import mfa + app.register_blueprint(mfa, url_prefix="/mfa") + return app diff --git a/app/auth/utils.py b/app/auth/utils.py index c8a0c4bf8..00a35ff22 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -526,9 +526,9 @@ def saml_acs(saml_sp, onelogin_request): self_url = OneLogin_Saml2_Utils.get_self_url(onelogin_request) if 'RelayState' in request.form and self_url != request.form['RelayState']: - return redirect(saml_sp.redirect_to(request.form['RelayState'], {'fresh_login': 'true'})) + return redirect(saml_sp.redirect_to(request.form['RelayState'], {'verify_mfa': 'true'})) - return redirect(url_for('main.index', fresh_login=True)) + return redirect(url_for('main.index', verify_mfa=True)) error_message = "Errors on SAML ACS:\n{errors}\n\nUser Data: {user_data}".format(errors='\n'.join(errors), user_data=user_data) diff --git a/app/constants/event_type.py b/app/constants/event_type.py index 1ac9e99c7..dea652182 100644 --- a/app/constants/event_type.py +++ b/app/constants/event_type.py @@ -60,6 +60,8 @@ ENVELOPE_CREATED = 'envelope_created' RESPONSE_LETTER_CREATED = 'response_letter_created' REOPENING_LETTER_CREATED = 'reopening_letter_created' +MFA_DEVICE_ADDED = 'mfa_device_added' +MFA_DEVICE_REMOVED = 'mfa_device_removed' FOR_REQUEST_HISTORY = [ USER_ADDED, diff --git a/app/main/views.py b/app/main/views.py index a4b78e770..94a6346f2 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -6,13 +6,14 @@ from flask import ( current_app, flash, + redirect, render_template, request, - session + session, + url_for, ) -from flask_login import current_user, login_required +from flask_login import current_user -import pyotp from app.constants import OPENRECORDS_DL_EMAIL from app.constants.response_privacy import PRIVATE from app.lib.db_utils import create_object, update_object @@ -22,27 +23,26 @@ from . import main -@main.route('/mfa', methods=['GET', 'POST']) -@login_required -def mfa(): - secret = pyotp.random_base32() - qr_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=current_user.email, issuer_name='OpenRecords') - return render_template('main/qr.html', qr_uri=qr_uri) - - @main.route('/', methods=['GET', 'POST']) def index(): fresh_login = request.args.get('fresh_login', False) - if current_user.is_authenticated and fresh_login: - if current_user.session_id is not None: - return render_template('main/home.html', duplicate_session=True) - update_object( - { - 'session_id': session.sid - }, - Users, - current_user.guid - ) + verify_mfa = request.args.get('verify_mfa', False) + if current_user.is_authenticated: + if verify_mfa: + if not current_user.has_mfa: + return redirect(url_for('mfa.register')) + else: + return redirect(url_for('mfa.verify')) + if fresh_login: + if current_user.session_id is not None: + return render_template('main/home.html', duplicate_session=True) + update_object( + { + 'session_id': session.sid + }, + Users, + current_user.guid + ) return render_template('main/home.html') diff --git a/app/mfa/__init__.py b/app/mfa/__init__.py new file mode 100644 index 000000000..f643ab2e9 --- /dev/null +++ b/app/mfa/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +mfa = Blueprint('mfa', __name__) + +from . import views diff --git a/app/mfa/forms.py b/app/mfa/forms.py new file mode 100644 index 000000000..ee928b9b0 --- /dev/null +++ b/app/mfa/forms.py @@ -0,0 +1,29 @@ +from flask_login import current_user +from flask_wtf import FlaskForm +from wtforms import HiddenField, SelectField, StringField +from wtforms.validators import ( + Length, + DataRequired, +) + +from app.models import MFA + + +class RegisterMFAForm(FlaskForm): + device_name = StringField('Device Name', validators=[Length(max=32), DataRequired()]) + mfa_secret = HiddenField(validators=[Length(max=32), DataRequired()]) + + +class VerifyMFAForm(FlaskForm): + device = SelectField('Device', validators=[DataRequired()]) + code = StringField('Code', validators=[Length(min=6, max=6), DataRequired()]) + + def __init__(self): + super(VerifyMFAForm, self).__init__() + + self.device.choices = [ + (mfa.device_name, + mfa.device_name) for mfa in MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).all() + ] + self.device.choices.insert(0, ('', '')) diff --git a/app/mfa/views.py b/app/mfa/views.py new file mode 100644 index 000000000..28e50525a --- /dev/null +++ b/app/mfa/views.py @@ -0,0 +1,121 @@ +from datetime import datetime + +import pyotp +from cryptography.fernet import Fernet +from flask import current_app, flash, redirect, request, render_template, session, url_for +from flask_login import current_user, login_required + +from app.constants import event_type +from app.lib.db_utils import create_object, update_object +from app.mfa import mfa +from app.mfa.forms import RegisterMFAForm, VerifyMFAForm +from app.models import Events, MFA + + +@mfa.route('/', methods=['GET', 'POST']) +@login_required +def register(): + form = RegisterMFAForm() + + if request.method == 'POST': + if form.validate_on_submit(): + device_name = form.device_name.data + secret = form.mfa_secret.data.encode() + + f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) + + create_object( + MFA( + user_guid=current_user.guid, + secret=f.encrypt(secret), + device_name=device_name, + is_valid=True, + ) + ) + create_object( + Events( + request_id=None, + user_guid=current_user.guid, + type_=event_type.MFA_DEVICE_ADDED, + timestamp=datetime.utcnow(), + new_value={'device_name': device_name, 'is_valid': True}, + ) + ) + return redirect(url_for('mfa.verify')) + else: + mfa_secret = pyotp.random_base32() + qr_uri = pyotp.totp.TOTP(mfa_secret).provisioning_uri(name=current_user.email, + issuer_name='OpenRecords') + form.mfa_secret.data = mfa_secret + return render_template('mfa/register.html', + form=form, + mfa_secret=mfa_secret, + qr_uri=qr_uri) + + +@mfa.route('/verify', methods=['GET', 'POST']) +@login_required +def verify(): + form = VerifyMFAForm() + + if request.method == 'POST': + if form.validate_on_submit(): + mfa = MFA.query.filter_by(user_guid=current_user.guid, + device_name=form.device.data, + is_valid=True).one_or_none() + f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) + + secret_str = f.decrypt(mfa.secret).decode('utf-8') + pyotp_verify = pyotp.TOTP(secret_str).verify(form.code.data) + if pyotp_verify: + session['mfa_verified'] = True + return redirect(url_for('main.index', fresh_login=True)) + flash("Invalid code. Please try again.", category='danger') + form.code.data = '' + return render_template('mfa/verify.html', + form=form) + else: + mfa = MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).first() + if mfa is None: + return redirect(url_for('mfa.register')) + return render_template('mfa/verify.html', + form=form) + + +@mfa.route('/manage', methods=['GET']) +@login_required +def manage(): + mfas = MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).all() + return render_template('mfa/manage.html', + mfas=mfas) + + +@mfa.route('/remove', methods=['POST']) +@login_required +def remove(): + device_name = request.form.get('device-name') + mfa = MFA.query.filter_by(user_guid=current_user.guid, + device_name=device_name, + is_valid=True).one_or_none() + if mfa is not None: + update_object( + {'is_valid': False}, + MFA, + current_user.guid + ) + create_object( + Events( + request_id=None, + user_guid=current_user.guid, + type_=event_type.MFA_DEVICE_REMOVED, + timestamp=datetime.utcnow(), + previous_value={'device_name': device_name, 'is_valid': True}, + new_value={'device_name': device_name, 'is_valid': False}, + ) + ) + flash('The device was removed.', category='success') + else: + flash('Something went wrong. Please try again.', category='danger') + return redirect(url_for('mfa.manage')) diff --git a/app/models.py b/app/models.py index 85a4c55db..1e21cabe1 100644 --- a/app/models.py +++ b/app/models.py @@ -384,6 +384,10 @@ def is_authenticated(self): if current_app.config["USE_SAML"]: if session.get("samlUserdata", None): return True + # if session.get("verifiedMFA", None): + # return True + # from flask import redirect, url_for + # return redirect(url_for('mfa.verify')) if current_app.config["USE_LOCAL_AUTH"]: return True return False @@ -557,6 +561,18 @@ def formatted_point_of_contact_number(self): def get_id(self): return self.guid + @property + def has_mfa(self): + """ + Determine if a user has MFA set up. + :return: Boolean + """ + mfa = MFA.query.filter_by(user_guid=self.guid, + is_valid=True).first() + if mfa is not None: + return True + return False + def es_update(self): """ Call es_update for any request where this user is the requester @@ -2040,7 +2056,7 @@ def populate(cls, json_name=None): class MFA(db.Model): __tablename__ = "mfa" user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) - secret = db.Column(db.String(32), nullable=False) + secret = db.Column(db.String(32), nullable=False) # TODO: increase length and possibly change to large binary device_name = db.Column(db.String(32), nullable=False) is_valid = db.Column(db.Boolean(), nullable=False, default=False) diff --git a/app/static/styles/mfa.css b/app/static/styles/mfa.css new file mode 100644 index 000000000..b1c6d55df --- /dev/null +++ b/app/static/styles/mfa.css @@ -0,0 +1,13 @@ +.row.mfa-fields { + font-weight: bold; + padding-bottom: 1em; +} + +.row.mfa-info { + padding-top: 1em; + padding-bottom: 1em; +} + +.row.mfa-info:nth-child(even) { + background-color: gainsboro; +} diff --git a/app/templates/main/verify_mfa.html b/app/templates/main/verify_mfa.html deleted file mode 100644 index 5c65618b4..000000000 --- a/app/templates/main/verify_mfa.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Verify MFA{% endblock %} - -{% block content %} -
-
- - - -
- -
-
-{% endblock %} - -{% block custom_script %} -{% endblock %} \ No newline at end of file diff --git a/app/templates/mfa/manage.html b/app/templates/mfa/manage.html new file mode 100644 index 000000000..6eb9d01d0 --- /dev/null +++ b/app/templates/mfa/manage.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Manage MFA{% endblock %} + +{% block custom_css %} + +{% endblock %} + +{% block content %} +
+

Manage MFA Devices

+
+ Add Device +
+
+
Device Name
+
+ {% for mfa in mfas %} +
+
+ {{ mfa.device_name }} +
+
+
+ + + +
+
+
+ {% endfor %} +
+{% endblock %} + +{% block custom_script %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/main/qr.html b/app/templates/mfa/register.html similarity index 69% rename from app/templates/main/qr.html rename to app/templates/mfa/register.html index a4be4a2e7..4fe26a386 100644 --- a/app/templates/main/qr.html +++ b/app/templates/mfa/register.html @@ -8,10 +8,10 @@

Register MFA Device

-
- - - + + {{ form.csrf_token }} + {{ form.device_name.label }} {{ form.device_name }} + {{ form.mfa_secret }}
diff --git a/app/templates/mfa/verify.html b/app/templates/mfa/verify.html new file mode 100644 index 000000000..fbaddd33c --- /dev/null +++ b/app/templates/mfa/verify.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Verify MFA{% endblock %} + +{% block content %} +
+
+

Verify MFA

+
+
+ {{ form.csrf_token }} + {{ form.device.label }} {{ form.device }} + {{ form.code.label }} {{ form.code }} +
+ +
+
+{% endblock %} diff --git a/config.py b/config.py index 5b309200c..4d7d51a57 100644 --- a/config.py +++ b/config.py @@ -61,6 +61,7 @@ class Config: PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 30))) SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" + MFA_ENCRYPT_KEY = os.environ.get('MFA_ENCRYPT_KEY').encode() # TODO: Change to using a file AUTH_TYPE = 'None' diff --git a/migrations/versions/d97f97be6905_add_mfa.py b/migrations/versions/d97f97be6905_add_mfa.py new file mode 100644 index 000000000..c858f0c53 --- /dev/null +++ b/migrations/versions/d97f97be6905_add_mfa.py @@ -0,0 +1,38 @@ +"""Add MFA + +Revision ID: d97f97be6905 +Revises: afde33bde2e0 +Create Date: 2020-12-07 17:05:32.720959 + +""" + +# revision identifiers, used by Alembic. +revision = 'd97f97be6905' +down_revision = 'afde33bde2e0' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('mfa', + sa.Column('user_guid', sa.String(length=64), nullable=False), + sa.Column('secret', sa.String(length=32), nullable=False), + sa.Column('device_name', sa.String(length=32), nullable=False), + sa.Column('is_valid', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], ), + sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], onupdate='CASCADE'), + sa.PrimaryKeyConstraint('user_guid') + ) + op.create_foreign_key(None, 'agency_users', 'users', ['user_guid'], ['guid']) + op.create_unique_constraint(None, 'users', ['guid']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'users', type_='unique') + op.drop_constraint(None, 'agency_users', type_='foreignkey') + op.drop_table('mfa') + # ### end Alembic commands ### From 6b7d0547c18e25686a14b92dc40b6c402ef48564 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Mon, 7 Dec 2020 17:09:34 -0500 Subject: [PATCH 05/44] Install pyotp and qrious; add MFA table; WIP views --- Pipfile | 1 + Pipfile.lock | 31 ++++++++++++++---- app/main/views.py | 11 ++++++- app/models.py | 22 +++++++++++++ app/static/js/plugins/qrious.min.js | 6 ++++ app/templates/main/qr.html | 32 +++++++++++++++++++ app/templates/main/verify_mfa.html | 18 +++++++++++ ..._user_requests_events_on_update_cascade.py | 8 ++--- 8 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 app/static/js/plugins/qrious.min.js create mode 100644 app/templates/main/qr.html create mode 100644 app/templates/main/verify_mfa.html diff --git a/Pipfile b/Pipfile index 7cbe4b951..9aca8f5a6 100644 --- a/Pipfile +++ b/Pipfile @@ -112,6 +112,7 @@ billiard = "*" kombu = "*" vine = "*" amqp = "*" +pyotp = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 2995aa757..386599c70 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c985a47a5c3cdc822ec0b89b975ccafdc418ea906740f127dcd73607667865fc" + "sha256": "66a3c54044861c8659527d586b8ad86596daddaa22d1190356155cc101658da0" }, "pipfile-spec": 6, "requires": { @@ -141,11 +141,11 @@ }, "celery": { "hashes": [ - "sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf", - "sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6" + "sha256:2cc87518fdd41375c8f40e2b0b59391568a3fb0a0b5f859f7bedef9b37efbc90", + "sha256:d7bcb0fca6fc94c41c946e8979c4c276a02cc7532c68757b43b25c2fa9eda68b" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.0.3" }, "certifi": { "hashes": [ @@ -215,6 +215,13 @@ ], "version": "==0.0.3" }, + "click-plugins": { + "hashes": [ + "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", + "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8" + ], + "version": "==1.1.1" + }, "click-repl": { "hashes": [ "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5", @@ -807,6 +814,14 @@ "index": "pypi", "version": "==19.0.0" }, + "pyotp": { + "hashes": [ + "sha256:038a3f70b34eaad3f72459e8b411662ef8dfcdd95f7d9203fa489e987a75584b", + "sha256:ef07c393660529261e66902e788b32e46260d2c29eb740978df778260a1c2b4c" + ], + "index": "pypi", + "version": "==2.4.1" + }, "pyparsing": { "hashes": [ "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", @@ -872,11 +887,13 @@ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" @@ -1059,11 +1076,11 @@ "develop": { "appnope": { "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442", + "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a" ], "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" + "version": "==0.1.2" }, "atomicwrites": { "hashes": [ diff --git a/app/main/views.py b/app/main/views.py index f75d2da4a..a4b78e770 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -10,8 +10,9 @@ request, session ) -from flask_login import current_user +from flask_login import current_user, login_required +import pyotp from app.constants import OPENRECORDS_DL_EMAIL from app.constants.response_privacy import PRIVATE from app.lib.db_utils import create_object, update_object @@ -21,6 +22,14 @@ from . import main +@main.route('/mfa', methods=['GET', 'POST']) +@login_required +def mfa(): + secret = pyotp.random_base32() + qr_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=current_user.email, issuer_name='OpenRecords') + return render_template('main/qr.html', qr_uri=qr_uri) + + @main.route('/', methods=['GET', 'POST']) def index(): fresh_login = request.args.get('fresh_login', False) diff --git a/app/models.py b/app/models.py index 60592fad3..85a4c55db 100644 --- a/app/models.py +++ b/app/models.py @@ -2035,3 +2035,25 @@ def populate(cls, json_name=None): ) db.session.add(custom_request_form) db.session.commit() + + +class MFA(db.Model): + __tablename__ = "mfa" + user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) + secret = db.Column(db.String(32), nullable=False) + device_name = db.Column(db.String(32), nullable=False) + is_valid = db.Column(db.Boolean(), nullable=False, default=False) + + __table_args__ = ( + db.ForeignKeyConstraint([user_guid], [Users.guid], onupdate="CASCADE"), + ) + + def __init__(self, + user_guid, + secret, + device_name, + is_valid): + self.user_guid = user_guid + self.secret = secret + self.device_name = device_name + self.is_valid = is_valid diff --git a/app/static/js/plugins/qrious.min.js b/app/static/js/plugins/qrious.min.js new file mode 100644 index 000000000..5735ea62d --- /dev/null +++ b/app/static/js/plugins/qrious.min.js @@ -0,0 +1,6 @@ +/*! QRious v4.0.2 | (C) 2017 Alasdair Mercer | GPL v3 License +Based on jsqrencode | (C) 2010 tz@execpc.com | GPL v3 License +*/ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.QRious=e()}(this,function(){"use strict";function t(t,e){var n;return"function"==typeof Object.create?n=Object.create(t):(s.prototype=t,n=new s,s.prototype=null),e&&i(!0,n,e),n}function e(e,n,s,r){var o=this;return"string"!=typeof e&&(r=s,s=n,n=e,e=null),"function"!=typeof n&&(r=s,s=n,n=function(){return o.apply(this,arguments)}),i(!1,n,o,r),n.prototype=t(o.prototype,s),n.prototype.constructor=n,n.class_=e||o.class_,n.super_=o,n}function i(t,e,i){for(var n,s,a=0,h=(i=o.call(arguments,2)).length;a>1&1,n=0;n0;e--)n[e]=n[e]?n[e-1]^_.EXPONENT[v._modN(_.LOG[n[e]]+t)]:n[e-1];n[0]=_.EXPONENT[v._modN(_.LOG[n[0]]+t)]}for(t=0;t<=i;t++)n[t]=_.LOG[n[t]]},_checkBadness:function(){var t,e,i,n,s,r=0,o=this._badness,a=this.buffer,h=this.width;for(s=0;sh*h;)u-=h*h,c++;for(r+=c*v.N4,n=0;n=o-2&&(t=o-2,s>9&&t--);var a=t;if(s>9){for(r[a+2]=0,r[a+3]=0;a--;)e=r[a],r[a+3]|=255&e<<4,r[a+2]=e>>4;r[2]|=255&t<<4,r[1]=t>>4,r[0]=64|t>>12}else{for(r[a+1]=0,r[a+2]=0;a--;)e=r[a],r[a+2]|=255&e<<4,r[a+1]=e>>4;r[1]|=255&t<<4,r[0]=64|t>>4}for(a=t+3-(s<10);a=5&&(i+=v.N1+n[e]-5);for(e=3;et||3*n[e-3]>=4*n[e]||3*n[e+3]>=4*n[e])&&(i+=v.N3);return i},_finish:function(){this._stringBuffer=this.buffer.slice();var t,e,i=0,n=3e4;for(e=0;e<8&&(this._applyMask(e),(t=this._checkBadness())>=1)1&n&&(s[r-1-e+8*r]=1,e<6?s[8+r*e]=1:s[8+r*(e+1)]=1);for(e=0;e<7;e++,n>>=1)1&n&&(s[8+r*(r-7+e)]=1,e?s[6-e+8*r]=1:s[7+8*r]=1)},_interleaveBlocks:function(){var t,e,i=this._dataBlock,n=this._ecc,s=this._eccBlock,r=0,o=this._calculateMaxLength(),a=this._neccBlock1,h=this._neccBlock2,f=this._stringBuffer;for(t=0;t1)for(t=u.BLOCK[n],i=s-7;;){for(e=s-7;e>t-3&&(this._addAlignment(e,i),!(e6)for(t=d.BLOCK[r-7],e=17,i=0;i<6;i++)for(n=0;n<3;n++,e--)1&(e>11?r>>e-12:t>>e)?(s[5-i+o*(2-n+o-11)]=1,s[2-n+o-11+o*(5-i)]=1):(this._setMask(5-i,2-n+o-11),this._setMask(2-n+o-11,5-i))},_isMasked:function(t,e){var i=v._getMaskBit(t,e);return 1===this._mask[i]},_pack:function(){var t,e,i,n=1,s=1,r=this.width,o=r-1,a=r-1,h=(this._dataBlock+this._eccBlock)*(this._neccBlock1+this._neccBlock2)+this._neccBlock2;for(e=0;ee&&(i=t,t=e,e=i),i=e,i+=e*e,i>>=1,i+=t},_modN:function(t){for(;t>=255;)t=((t-=255)>>8)+(255&t);return t},N1:3,N2:3,N3:40,N4:10}),p=v,m=f.extend({draw:function(){this.element.src=this.qrious.toDataURL()},reset:function(){this.element.src=""},resize:function(){var t=this.element;t.width=t.height=this.qrious.size}}),g=h.extend(function(t,e,i,n){this.name=t,this.modifiable=Boolean(e),this.defaultValue=i,this._valueTransformer=n},{transform:function(t){var e=this._valueTransformer;return"function"==typeof e?e(t,this):t}}),k=h.extend(null,{abs:function(t){return null!=t?Math.abs(t):null},hasOwn:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},noop:function(){},toUpperCase:function(t){return null!=t?t.toUpperCase():null}}),w=h.extend(function(t){this.options={},t.forEach(function(t){this.options[t.name]=t},this)},{exists:function(t){return null!=this.options[t]},get:function(t,e){return w._get(this.options[t],e)},getAll:function(t){var e,i=this.options,n={};for(e in i)k.hasOwn(i,e)&&(n[e]=w._get(i[e],t));return n},init:function(t,e,i){"function"!=typeof i&&(i=k.noop);var n,s;for(n in this.options)k.hasOwn(this.options,n)&&(s=this.options[n],w._set(s,s.defaultValue,e),w._createAccessor(s,e,i));this._setAll(t,e,!0)},set:function(t,e,i){return this._set(t,e,i)},setAll:function(t,e){return this._setAll(t,e)},_set:function(t,e,i,n){var s=this.options[t];if(!s)throw new Error("Invalid option: "+t);if(!s.modifiable&&!n)throw new Error("Option cannot be modified: "+t);return w._set(s,e,i)},_setAll:function(t,e,i){if(!t)return!1;var n,s=!1;for(n in t)k.hasOwn(t,n)&&this._set(n,t[n],e,i)&&(s=!0);return s}},{_createAccessor:function(t,e,i){var n={get:function(){return w._get(t,e)}};t.modifiable&&(n.set=function(n){w._set(t,n,e)&&i(n,t)}),Object.defineProperty(e,t.name,n)},_get:function(t,e){return e["_"+t.name]},_set:function(t,e,i){var n="_"+t.name,s=i[n],r=t.transform(null!=e?e:t.defaultValue);return i[n]=r,r!==s}}),M=w,b=h.extend(function(){this._services={}},{getService:function(t){var e=this._services[t];if(!e)throw new Error("Service is not being managed with name: "+t);return e},setService:function(t,e){if(this._services[t])throw new Error("Service is already managed with name: "+t);e&&(this._services[t]=e)}}),B=new M([new g("background",!0,"white"),new g("backgroundAlpha",!0,1,k.abs),new g("element"),new g("foreground",!0,"black"),new g("foregroundAlpha",!0,1,k.abs),new g("level",!0,"L",k.toUpperCase),new g("mime",!0,"image/png"),new g("padding",!0,null,k.abs),new g("size",!0,100,k.abs),new g("value",!0,"")]),y=new b,O=h.extend(function(t){B.init(t,this,this.update.bind(this));var e=B.get("element",this),i=y.getService("element"),n=e&&i.isCanvas(e)?e:i.createCanvas(),s=e&&i.isImage(e)?e:i.createImage();this._canvasRenderer=new c(this,n,!0),this._imageRenderer=new m(this,s,s===e),this.update()},{get:function(){return B.getAll(this)},set:function(t){B.setAll(t,this)&&this.update()},toDataURL:function(t){return this.canvas.toDataURL(t||this.mime)},update:function(){var t=new p({level:this.level,value:this.value});this._canvasRenderer.render(t),this._imageRenderer.render(t)}},{use:function(t){y.setService(t.getName(),t)}});Object.defineProperties(O.prototype,{canvas:{get:function(){return this._canvasRenderer.getElement()}},image:{get:function(){return this._imageRenderer.getElement()}}});var A=O,L=h.extend({getName:function(){}}).extend({createCanvas:function(){},createImage:function(){},getName:function(){return"element"},isCanvas:function(t){},isImage:function(t){}}).extend({createCanvas:function(){return document.createElement("canvas")},createImage:function(){return document.createElement("img")},isCanvas:function(t){return t instanceof HTMLCanvasElement},isImage:function(t){return t instanceof HTMLImageElement}});return A.use(new L),A}); + +//# sourceMappingURL=qrious.min.js.map \ No newline at end of file diff --git a/app/templates/main/qr.html b/app/templates/main/qr.html new file mode 100644 index 000000000..a4be4a2e7 --- /dev/null +++ b/app/templates/main/qr.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}MFA Setup{% endblock %} + +{% block content %} +
+
+

Register MFA Device

+ +
+
+ + + +
+ +
+
+{% endblock %} + +{% block custom_script %} + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/main/verify_mfa.html b/app/templates/main/verify_mfa.html new file mode 100644 index 000000000..5c65618b4 --- /dev/null +++ b/app/templates/main/verify_mfa.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Verify MFA{% endblock %} + +{% block content %} +
+
+ + + +
+ +
+
+{% endblock %} + +{% block custom_script %} +{% endblock %} \ No newline at end of file diff --git a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py index f8cd9de5c..7d2b5cd87 100644 --- a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py +++ b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py @@ -16,7 +16,7 @@ def upgrade(): ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") + # op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") op.create_foreign_key( None, "events", @@ -25,9 +25,9 @@ def upgrade(): ["guid", "auth_user_type"], onupdate="CASCADE", ) - op.drop_constraint( - "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" - ) + #op.drop_constraint( + # "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" + #) op.create_foreign_key( None, "user_requests", From 436a08c7dd9a0e5af99122ac7f5d027d9f6607ce Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 23 Dec 2020 16:58:08 -0500 Subject: [PATCH 06/44] Create MFA blueprint and implement MFA backend functionality --- app/__init__.py | 11 +- app/auth/utils.py | 4 +- app/constants/event_type.py | 2 + app/main/views.py | 42 +++--- app/mfa/__init__.py | 5 + app/mfa/forms.py | 29 +++++ app/mfa/views.py | 121 ++++++++++++++++++ app/models.py | 18 ++- app/static/styles/mfa.css | 13 ++ app/templates/main/verify_mfa.html | 18 --- app/templates/mfa/manage.html | 36 ++++++ .../{main/qr.html => mfa/register.html} | 8 +- app/templates/mfa/verify.html | 18 +++ config.py | 1 + migrations/versions/d97f97be6905_add_mfa.py | 38 ++++++ 15 files changed, 317 insertions(+), 47 deletions(-) create mode 100644 app/mfa/__init__.py create mode 100644 app/mfa/forms.py create mode 100644 app/mfa/views.py create mode 100644 app/static/styles/mfa.css delete mode 100644 app/templates/main/verify_mfa.html create mode 100644 app/templates/mfa/manage.html rename app/templates/{main/qr.html => mfa/register.html} (69%) create mode 100644 app/templates/mfa/verify.html create mode 100644 migrations/versions/d97f97be6905_add_mfa.py diff --git a/app/__init__.py b/app/__init__.py index cdd399b8a..7c0d23b9f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,7 +9,7 @@ import redis from business_calendar import Calendar, MO, TU, WE, TH, FR from celery import Celery -from flask import (Flask, abort, render_template, request as flask_request) +from flask import (Flask, abort, redirect, render_template, request as flask_request, session, url_for) from flask_bootstrap import Bootstrap from flask_elasticsearch import FlaskElasticsearch from flask_login import LoginManager, current_user @@ -175,6 +175,12 @@ def check_maintenance_mode(): if not flask_request.cookies.get('authorized_maintainer', None): return abort(503) + @app.before_request + def check_valid_login(): + if current_user.is_authenticated: + if not session.get('mfa_verified', False) and flask_request.endpoint not in ['mfa.register', 'mfa.verify', 'static', 'auth.logout']: + return redirect(url_for('mfa.verify')) + @app.context_processor def add_session_config(): """Add current_app.permanent_session_lifetime converted to milliseconds @@ -236,4 +242,7 @@ def add_debug(): from .permissions import permissions app.register_blueprint(permissions, url_prefix="/permissions/api/v1.0") + from .mfa import mfa + app.register_blueprint(mfa, url_prefix="/mfa") + return app diff --git a/app/auth/utils.py b/app/auth/utils.py index c8a0c4bf8..00a35ff22 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -526,9 +526,9 @@ def saml_acs(saml_sp, onelogin_request): self_url = OneLogin_Saml2_Utils.get_self_url(onelogin_request) if 'RelayState' in request.form and self_url != request.form['RelayState']: - return redirect(saml_sp.redirect_to(request.form['RelayState'], {'fresh_login': 'true'})) + return redirect(saml_sp.redirect_to(request.form['RelayState'], {'verify_mfa': 'true'})) - return redirect(url_for('main.index', fresh_login=True)) + return redirect(url_for('main.index', verify_mfa=True)) error_message = "Errors on SAML ACS:\n{errors}\n\nUser Data: {user_data}".format(errors='\n'.join(errors), user_data=user_data) diff --git a/app/constants/event_type.py b/app/constants/event_type.py index 1ac9e99c7..dea652182 100644 --- a/app/constants/event_type.py +++ b/app/constants/event_type.py @@ -60,6 +60,8 @@ ENVELOPE_CREATED = 'envelope_created' RESPONSE_LETTER_CREATED = 'response_letter_created' REOPENING_LETTER_CREATED = 'reopening_letter_created' +MFA_DEVICE_ADDED = 'mfa_device_added' +MFA_DEVICE_REMOVED = 'mfa_device_removed' FOR_REQUEST_HISTORY = [ USER_ADDED, diff --git a/app/main/views.py b/app/main/views.py index a4b78e770..94a6346f2 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -6,13 +6,14 @@ from flask import ( current_app, flash, + redirect, render_template, request, - session + session, + url_for, ) -from flask_login import current_user, login_required +from flask_login import current_user -import pyotp from app.constants import OPENRECORDS_DL_EMAIL from app.constants.response_privacy import PRIVATE from app.lib.db_utils import create_object, update_object @@ -22,27 +23,26 @@ from . import main -@main.route('/mfa', methods=['GET', 'POST']) -@login_required -def mfa(): - secret = pyotp.random_base32() - qr_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=current_user.email, issuer_name='OpenRecords') - return render_template('main/qr.html', qr_uri=qr_uri) - - @main.route('/', methods=['GET', 'POST']) def index(): fresh_login = request.args.get('fresh_login', False) - if current_user.is_authenticated and fresh_login: - if current_user.session_id is not None: - return render_template('main/home.html', duplicate_session=True) - update_object( - { - 'session_id': session.sid - }, - Users, - current_user.guid - ) + verify_mfa = request.args.get('verify_mfa', False) + if current_user.is_authenticated: + if verify_mfa: + if not current_user.has_mfa: + return redirect(url_for('mfa.register')) + else: + return redirect(url_for('mfa.verify')) + if fresh_login: + if current_user.session_id is not None: + return render_template('main/home.html', duplicate_session=True) + update_object( + { + 'session_id': session.sid + }, + Users, + current_user.guid + ) return render_template('main/home.html') diff --git a/app/mfa/__init__.py b/app/mfa/__init__.py new file mode 100644 index 000000000..f643ab2e9 --- /dev/null +++ b/app/mfa/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +mfa = Blueprint('mfa', __name__) + +from . import views diff --git a/app/mfa/forms.py b/app/mfa/forms.py new file mode 100644 index 000000000..ee928b9b0 --- /dev/null +++ b/app/mfa/forms.py @@ -0,0 +1,29 @@ +from flask_login import current_user +from flask_wtf import FlaskForm +from wtforms import HiddenField, SelectField, StringField +from wtforms.validators import ( + Length, + DataRequired, +) + +from app.models import MFA + + +class RegisterMFAForm(FlaskForm): + device_name = StringField('Device Name', validators=[Length(max=32), DataRequired()]) + mfa_secret = HiddenField(validators=[Length(max=32), DataRequired()]) + + +class VerifyMFAForm(FlaskForm): + device = SelectField('Device', validators=[DataRequired()]) + code = StringField('Code', validators=[Length(min=6, max=6), DataRequired()]) + + def __init__(self): + super(VerifyMFAForm, self).__init__() + + self.device.choices = [ + (mfa.device_name, + mfa.device_name) for mfa in MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).all() + ] + self.device.choices.insert(0, ('', '')) diff --git a/app/mfa/views.py b/app/mfa/views.py new file mode 100644 index 000000000..28e50525a --- /dev/null +++ b/app/mfa/views.py @@ -0,0 +1,121 @@ +from datetime import datetime + +import pyotp +from cryptography.fernet import Fernet +from flask import current_app, flash, redirect, request, render_template, session, url_for +from flask_login import current_user, login_required + +from app.constants import event_type +from app.lib.db_utils import create_object, update_object +from app.mfa import mfa +from app.mfa.forms import RegisterMFAForm, VerifyMFAForm +from app.models import Events, MFA + + +@mfa.route('/', methods=['GET', 'POST']) +@login_required +def register(): + form = RegisterMFAForm() + + if request.method == 'POST': + if form.validate_on_submit(): + device_name = form.device_name.data + secret = form.mfa_secret.data.encode() + + f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) + + create_object( + MFA( + user_guid=current_user.guid, + secret=f.encrypt(secret), + device_name=device_name, + is_valid=True, + ) + ) + create_object( + Events( + request_id=None, + user_guid=current_user.guid, + type_=event_type.MFA_DEVICE_ADDED, + timestamp=datetime.utcnow(), + new_value={'device_name': device_name, 'is_valid': True}, + ) + ) + return redirect(url_for('mfa.verify')) + else: + mfa_secret = pyotp.random_base32() + qr_uri = pyotp.totp.TOTP(mfa_secret).provisioning_uri(name=current_user.email, + issuer_name='OpenRecords') + form.mfa_secret.data = mfa_secret + return render_template('mfa/register.html', + form=form, + mfa_secret=mfa_secret, + qr_uri=qr_uri) + + +@mfa.route('/verify', methods=['GET', 'POST']) +@login_required +def verify(): + form = VerifyMFAForm() + + if request.method == 'POST': + if form.validate_on_submit(): + mfa = MFA.query.filter_by(user_guid=current_user.guid, + device_name=form.device.data, + is_valid=True).one_or_none() + f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) + + secret_str = f.decrypt(mfa.secret).decode('utf-8') + pyotp_verify = pyotp.TOTP(secret_str).verify(form.code.data) + if pyotp_verify: + session['mfa_verified'] = True + return redirect(url_for('main.index', fresh_login=True)) + flash("Invalid code. Please try again.", category='danger') + form.code.data = '' + return render_template('mfa/verify.html', + form=form) + else: + mfa = MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).first() + if mfa is None: + return redirect(url_for('mfa.register')) + return render_template('mfa/verify.html', + form=form) + + +@mfa.route('/manage', methods=['GET']) +@login_required +def manage(): + mfas = MFA.query.filter_by(user_guid=current_user.guid, + is_valid=True).all() + return render_template('mfa/manage.html', + mfas=mfas) + + +@mfa.route('/remove', methods=['POST']) +@login_required +def remove(): + device_name = request.form.get('device-name') + mfa = MFA.query.filter_by(user_guid=current_user.guid, + device_name=device_name, + is_valid=True).one_or_none() + if mfa is not None: + update_object( + {'is_valid': False}, + MFA, + current_user.guid + ) + create_object( + Events( + request_id=None, + user_guid=current_user.guid, + type_=event_type.MFA_DEVICE_REMOVED, + timestamp=datetime.utcnow(), + previous_value={'device_name': device_name, 'is_valid': True}, + new_value={'device_name': device_name, 'is_valid': False}, + ) + ) + flash('The device was removed.', category='success') + else: + flash('Something went wrong. Please try again.', category='danger') + return redirect(url_for('mfa.manage')) diff --git a/app/models.py b/app/models.py index 85a4c55db..1e21cabe1 100644 --- a/app/models.py +++ b/app/models.py @@ -384,6 +384,10 @@ def is_authenticated(self): if current_app.config["USE_SAML"]: if session.get("samlUserdata", None): return True + # if session.get("verifiedMFA", None): + # return True + # from flask import redirect, url_for + # return redirect(url_for('mfa.verify')) if current_app.config["USE_LOCAL_AUTH"]: return True return False @@ -557,6 +561,18 @@ def formatted_point_of_contact_number(self): def get_id(self): return self.guid + @property + def has_mfa(self): + """ + Determine if a user has MFA set up. + :return: Boolean + """ + mfa = MFA.query.filter_by(user_guid=self.guid, + is_valid=True).first() + if mfa is not None: + return True + return False + def es_update(self): """ Call es_update for any request where this user is the requester @@ -2040,7 +2056,7 @@ def populate(cls, json_name=None): class MFA(db.Model): __tablename__ = "mfa" user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) - secret = db.Column(db.String(32), nullable=False) + secret = db.Column(db.String(32), nullable=False) # TODO: increase length and possibly change to large binary device_name = db.Column(db.String(32), nullable=False) is_valid = db.Column(db.Boolean(), nullable=False, default=False) diff --git a/app/static/styles/mfa.css b/app/static/styles/mfa.css new file mode 100644 index 000000000..b1c6d55df --- /dev/null +++ b/app/static/styles/mfa.css @@ -0,0 +1,13 @@ +.row.mfa-fields { + font-weight: bold; + padding-bottom: 1em; +} + +.row.mfa-info { + padding-top: 1em; + padding-bottom: 1em; +} + +.row.mfa-info:nth-child(even) { + background-color: gainsboro; +} diff --git a/app/templates/main/verify_mfa.html b/app/templates/main/verify_mfa.html deleted file mode 100644 index 5c65618b4..000000000 --- a/app/templates/main/verify_mfa.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Verify MFA{% endblock %} - -{% block content %} -
-
- - - -
- -
-
-{% endblock %} - -{% block custom_script %} -{% endblock %} \ No newline at end of file diff --git a/app/templates/mfa/manage.html b/app/templates/mfa/manage.html new file mode 100644 index 000000000..6eb9d01d0 --- /dev/null +++ b/app/templates/mfa/manage.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Manage MFA{% endblock %} + +{% block custom_css %} + +{% endblock %} + +{% block content %} +
+

Manage MFA Devices

+
+ Add Device +
+
+
Device Name
+
+ {% for mfa in mfas %} +
+
+ {{ mfa.device_name }} +
+
+
+ + + +
+
+
+ {% endfor %} +
+{% endblock %} + +{% block custom_script %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/main/qr.html b/app/templates/mfa/register.html similarity index 69% rename from app/templates/main/qr.html rename to app/templates/mfa/register.html index a4be4a2e7..4fe26a386 100644 --- a/app/templates/main/qr.html +++ b/app/templates/mfa/register.html @@ -8,10 +8,10 @@

Register MFA Device

-
- - - + + {{ form.csrf_token }} + {{ form.device_name.label }} {{ form.device_name }} + {{ form.mfa_secret }}
diff --git a/app/templates/mfa/verify.html b/app/templates/mfa/verify.html new file mode 100644 index 000000000..fbaddd33c --- /dev/null +++ b/app/templates/mfa/verify.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Verify MFA{% endblock %} + +{% block content %} +
+
+

Verify MFA

+
+
+ {{ form.csrf_token }} + {{ form.device.label }} {{ form.device }} + {{ form.code.label }} {{ form.code }} +
+ +
+
+{% endblock %} diff --git a/config.py b/config.py index 5b309200c..4d7d51a57 100644 --- a/config.py +++ b/config.py @@ -61,6 +61,7 @@ class Config: PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 30))) SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" + MFA_ENCRYPT_KEY = os.environ.get('MFA_ENCRYPT_KEY').encode() # TODO: Change to using a file AUTH_TYPE = 'None' diff --git a/migrations/versions/d97f97be6905_add_mfa.py b/migrations/versions/d97f97be6905_add_mfa.py new file mode 100644 index 000000000..c858f0c53 --- /dev/null +++ b/migrations/versions/d97f97be6905_add_mfa.py @@ -0,0 +1,38 @@ +"""Add MFA + +Revision ID: d97f97be6905 +Revises: afde33bde2e0 +Create Date: 2020-12-07 17:05:32.720959 + +""" + +# revision identifiers, used by Alembic. +revision = 'd97f97be6905' +down_revision = 'afde33bde2e0' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('mfa', + sa.Column('user_guid', sa.String(length=64), nullable=False), + sa.Column('secret', sa.String(length=32), nullable=False), + sa.Column('device_name', sa.String(length=32), nullable=False), + sa.Column('is_valid', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], ), + sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], onupdate='CASCADE'), + sa.PrimaryKeyConstraint('user_guid') + ) + op.create_foreign_key(None, 'agency_users', 'users', ['user_guid'], ['guid']) + op.create_unique_constraint(None, 'users', ['guid']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'users', type_='unique') + op.drop_constraint(None, 'agency_users', type_='foreignkey') + op.drop_table('mfa') + # ### end Alembic commands ### From 130e019b0bd25d2bc9601b530227b268711ca0f7 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Mon, 4 Jan 2021 16:40:46 -0500 Subject: [PATCH 07/44] Update MFA secret column to LargeBinary datatype --- app/main/views.py | 6 +++--- app/models.py | 3 ++- ...f97be6905_add_mfa.py => 199f0080036a_add_mfa.py} | 13 ++++--------- 3 files changed, 9 insertions(+), 13 deletions(-) rename migrations/versions/{d97f97be6905_add_mfa.py => 199f0080036a_add_mfa.py} (63%) diff --git a/app/main/views.py b/app/main/views.py index 94a6346f2..0a4a7a5aa 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -29,10 +29,10 @@ def index(): verify_mfa = request.args.get('verify_mfa', False) if current_user.is_authenticated: if verify_mfa: - if not current_user.has_mfa: - return redirect(url_for('mfa.register')) - else: + if current_user.has_mfa: return redirect(url_for('mfa.verify')) + else: + return redirect(url_for('mfa.register')) if fresh_login: if current_user.session_id is not None: return render_template('main/home.html', duplicate_session=True) diff --git a/app/models.py b/app/models.py index 1e21cabe1..3d2a091f7 100644 --- a/app/models.py +++ b/app/models.py @@ -372,6 +372,7 @@ class Users(UserMixin, db.Model): lazy="dynamic", ) agency_users = db.relationship("AgencyUsers", backref="user", lazy="dynamic") + mfa = db.relationship("MFA", backref="user", lazy="dynamic") @property def is_authenticated(self): @@ -2056,7 +2057,7 @@ def populate(cls, json_name=None): class MFA(db.Model): __tablename__ = "mfa" user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) - secret = db.Column(db.String(32), nullable=False) # TODO: increase length and possibly change to large binary + secret = db.Column(db.LargeBinary(), nullable=False) device_name = db.Column(db.String(32), nullable=False) is_valid = db.Column(db.Boolean(), nullable=False, default=False) diff --git a/migrations/versions/d97f97be6905_add_mfa.py b/migrations/versions/199f0080036a_add_mfa.py similarity index 63% rename from migrations/versions/d97f97be6905_add_mfa.py rename to migrations/versions/199f0080036a_add_mfa.py index c858f0c53..850df9b7f 100644 --- a/migrations/versions/d97f97be6905_add_mfa.py +++ b/migrations/versions/199f0080036a_add_mfa.py @@ -1,13 +1,13 @@ """Add MFA -Revision ID: d97f97be6905 +Revision ID: 199f0080036a Revises: afde33bde2e0 -Create Date: 2020-12-07 17:05:32.720959 +Create Date: 2021-01-04 16:09:59.139962 """ # revision identifiers, used by Alembic. -revision = 'd97f97be6905' +revision = '199f0080036a' down_revision = 'afde33bde2e0' from alembic import op @@ -18,21 +18,16 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('mfa', sa.Column('user_guid', sa.String(length=64), nullable=False), - sa.Column('secret', sa.String(length=32), nullable=False), + sa.Column('secret', sa.LargeBinary(), nullable=False), sa.Column('device_name', sa.String(length=32), nullable=False), sa.Column('is_valid', sa.Boolean(), nullable=False), - sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], ), sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], onupdate='CASCADE'), sa.PrimaryKeyConstraint('user_guid') ) - op.create_foreign_key(None, 'agency_users', 'users', ['user_guid'], ['guid']) - op.create_unique_constraint(None, 'users', ['guid']) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'users', type_='unique') - op.drop_constraint(None, 'agency_users', type_='foreignkey') op.drop_table('mfa') # ### end Alembic commands ### From 6b3b4a6249434288c57c90a5dfb3492e06351e6e Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Mon, 4 Jan 2021 16:45:09 -0500 Subject: [PATCH 08/44] Remove old migration --- migrations/versions/d97f97be6905_add_mfa.py | 38 --------------------- 1 file changed, 38 deletions(-) delete mode 100644 migrations/versions/d97f97be6905_add_mfa.py diff --git a/migrations/versions/d97f97be6905_add_mfa.py b/migrations/versions/d97f97be6905_add_mfa.py deleted file mode 100644 index c858f0c53..000000000 --- a/migrations/versions/d97f97be6905_add_mfa.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Add MFA - -Revision ID: d97f97be6905 -Revises: afde33bde2e0 -Create Date: 2020-12-07 17:05:32.720959 - -""" - -# revision identifiers, used by Alembic. -revision = 'd97f97be6905' -down_revision = 'afde33bde2e0' - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('mfa', - sa.Column('user_guid', sa.String(length=64), nullable=False), - sa.Column('secret', sa.String(length=32), nullable=False), - sa.Column('device_name', sa.String(length=32), nullable=False), - sa.Column('is_valid', sa.Boolean(), nullable=False), - sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], ), - sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], onupdate='CASCADE'), - sa.PrimaryKeyConstraint('user_guid') - ) - op.create_foreign_key(None, 'agency_users', 'users', ['user_guid'], ['guid']) - op.create_unique_constraint(None, 'users', ['guid']) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'users', type_='unique') - op.drop_constraint(None, 'agency_users', type_='foreignkey') - op.drop_table('mfa') - # ### end Alembic commands ### From 350180578dc6e12a3fcfe02fd0180c4cb2ee75fe Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Tue, 5 Jan 2021 16:51:21 -0500 Subject: [PATCH 09/44] Use file for fernet key and create utils file for fernet methods --- app/lib/fernet_utils.py | 20 +++++++++++++++++++ app/mfa/views.py | 11 ++++------ config.py | 2 +- ..._user_requests_events_on_update_cascade.py | 8 ++++---- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 app/lib/fernet_utils.py diff --git a/app/lib/fernet_utils.py b/app/lib/fernet_utils.py new file mode 100644 index 000000000..270e11387 --- /dev/null +++ b/app/lib/fernet_utils.py @@ -0,0 +1,20 @@ +from cryptography.fernet import Fernet +from flask import current_app + + +def load_key(): + return open(current_app.config['MFA_ENCRYPT_FILE'], 'rb').read() + + +def encrypt_string(string): + key = load_key() + encoded_string = string.encode() + f = Fernet(key) + return f.encrypt(encoded_string) + + +def decrypt_string(encrypted_string): + key = load_key() + f = Fernet(key) + decrypted_string = f.decrypt(encrypted_string) + return decrypted_string.decode() diff --git a/app/mfa/views.py b/app/mfa/views.py index 28e50525a..94b4933aa 100644 --- a/app/mfa/views.py +++ b/app/mfa/views.py @@ -1,12 +1,12 @@ from datetime import datetime import pyotp -from cryptography.fernet import Fernet -from flask import current_app, flash, redirect, request, render_template, session, url_for +from flask import flash, redirect, request, render_template, session, url_for from flask_login import current_user, login_required from app.constants import event_type from app.lib.db_utils import create_object, update_object +from app.lib.fernet_utils import decrypt_string, encrypt_string from app.mfa import mfa from app.mfa.forms import RegisterMFAForm, VerifyMFAForm from app.models import Events, MFA @@ -22,12 +22,10 @@ def register(): device_name = form.device_name.data secret = form.mfa_secret.data.encode() - f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) - create_object( MFA( user_guid=current_user.guid, - secret=f.encrypt(secret), + secret=encrypt_string(secret), device_name=device_name, is_valid=True, ) @@ -63,9 +61,8 @@ def verify(): mfa = MFA.query.filter_by(user_guid=current_user.guid, device_name=form.device.data, is_valid=True).one_or_none() - f = Fernet(current_app.config['MFA_ENCRYPT_KEY']) - secret_str = f.decrypt(mfa.secret).decode('utf-8') + secret_str = decrypt_string(mfa.secret) pyotp_verify = pyotp.TOTP(secret_str).verify(form.code.data) if pyotp_verify: session['mfa_verified'] = True diff --git a/config.py b/config.py index 4d7d51a57..11beba071 100644 --- a/config.py +++ b/config.py @@ -61,7 +61,7 @@ class Config: PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 30))) SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" - MFA_ENCRYPT_KEY = os.environ.get('MFA_ENCRYPT_KEY').encode() # TODO: Change to using a file + MFA_ENCRYPT_FILE = os.environ.get('MFA_ENCRYPT_FILE') AUTH_TYPE = 'None' diff --git a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py index 7d2b5cd87..e3f91e7f2 100644 --- a/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py +++ b/migrations/versions/8797e9a71f62_user_requests_events_on_update_cascade.py @@ -16,7 +16,7 @@ def upgrade(): ### commands auto generated by Alembic - please adjust! ### - # op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") + op.drop_constraint("events_user_guid_fkey", "events", type_="foreignkey") op.create_foreign_key( None, "events", @@ -25,9 +25,9 @@ def upgrade(): ["guid", "auth_user_type"], onupdate="CASCADE", ) - #op.drop_constraint( - # "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" - #) + op.drop_constraint( + "user_requests_user_guid_fkey", "user_requests", type_="foreignkey" + ) op.create_foreign_key( None, "user_requests", From cf894301d5801d1d9fb95bb8d285ec29d6f69243 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Thu, 7 Jan 2021 16:32:17 -0500 Subject: [PATCH 10/44] Update python dependencies --- .python-version | 2 +- Pipfile | 200 +++---- Pipfile.lock | 1316 +++++++++++++++++++++++++---------------------- openrecords.py | 2 +- 4 files changed, 813 insertions(+), 707 deletions(-) diff --git a/.python-version b/.python-version index 87ce49290..269aa9c86 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.5.2 +3.8.3 diff --git a/Pipfile b/Pipfile index 9aca8f5a6..aefa490e8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,108 +4,108 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -pipdeptree = "==0.9.0" -decorator = "==4.0.11" -Faker = "==0.7.9" -ipdb = "==0.10.2" -ipython = "==5.3.0" -ipython-genutils = "==0.2.0" -pexpect = "==4.2.1" -pickleshare = "==0.7.4" -prompt-toolkit = "==1.0.13" -ptyprocess = "==0.5.1" -Pygments = "==2.2.0" -simplegeneric = "==0.8.1" -traitlets = "==4.3.2" -wcwidth = "==0.1.7" -pytest = "==3.6.0" -pytest-cov = "==2.5.1" -atomicwrites = "==1.3.0" -attrs = "==18.2.0" -more-itertools = "==5.0.0" -pluggy = "==0.6.0" -py = "==1.7.0" +pipdeptree = "*" +decorator = "*" +Faker = "*" +ipdb = "*" +ipython = "*" +ipython-genutils = "*" +pexpect = "*" +pickleshare = "*" +prompt-toolkit = "*" +ptyprocess = "*" +Pygments = "*" +simplegeneric = "*" +traitlets = "*" +wcwidth = "*" +pytest = "*" +pytest-cov = "*" +atomicwrites = "*" +attrs = "*" +more-itertools = "*" +pluggy = "*" +py = "*" [packages] -alembic = "==1.0.7" -anyjson = "==0.3.3" -appdirs = "==1.4.3" -asn1crypto = "==0.24.0" -bcrypt = "==3.1.6" -blinker = "==1.4" -cached-property = "==1.5.1" -cairocffi = "==0.9.0" -certifi = "==2018.11.29" -cffi = "==1.11.5" -chardet = "==3.0.4" -click = "==7.0" -coverage = "==4.5.2" -cryptography = "==2.5" -cssselect2 = "==0.2.1" -defusedxml = "==0.5.0" -dominate = "==2.3.5" -elasticsearch = "==6.3.1" -holidays = "==0.9.9" -html5lib = "==1.0.1" -idna = "==2.8" -isodate = "==0.6.0" -itsdangerous = "==1.1.0" -jsonschema = "==2.6.0" -ldap3 = "==2.5.2" -lxml = "==4.3.0" -oauthlib = "==3.0.1" -paramiko = "==2.4.2" -pdfrw = "==0.4" -pkgconfig = "==1.4.0" -pyasn1 = "==0.4.5" -pycparser = "==2.19" -pyparsing = "==2.3.1" -python-dateutil = "==2.8.0" -python-dotenv = "==0.10.1" -python-editor = "==1.0.4" -pytz = "==2018.9" -raven = "==6.10.0" -redis = "==3.1.0" -requests = "==2.21.0" -requests-oauthlib = "==1.2.0" -simplekv = "==0.11.11" -six = "==1.12.0" -tablib = "==0.13.0" -tinycss2 = "==0.6.1" -tzlocal = "==1.5.1" -urllib3 = "==1.24.1" -virtualenv = "==13.1.2" -visitor = "==0.1.3" -webencodings = "==0.5.1" -xmlsec = "==1.3.3" -business_calendar = "==0.2.1" -CairoSVG = "==2.3.0" -Flask = "==1.0.2" -Flask-Bootstrap = "==3.3.7.1" -Flask-Elasticsearch = "==0.2.5" -Flask-Login = "==0.4.1" -Flask-Mail = "==0.9.1" -Flask-Migrate = "==2.3.1" -Flask-Moment = "==0.6.0" -Flask-reCaptcha = "==0.4.2" -Flask-Script = "==2.0.6" -Flask-SQLAlchemy = "==2.3.2" -Flask-Tracy = "==0.1.3" -Flask-WeasyPrint = "==0.5" -Flask-WTF = "==0.14.2" -Jinja2 = "==2.10" -Mako = "==1.0.7" -MarkupSafe = "==1.1.0" -Pillow = "==5.4.1" -PyNaCl = "==1.3.0" -pyOpenSSL = "==19.0.0" -Pyphen = "==0.9.5" -SQLAlchemy = "==1.2.17" -WeasyPrint = "==45" -Werkzeug = "==0.14.1" -WTForms = "==2.2.1" +alembic = "*" +anyjson = "*" +appdirs = "*" +asn1crypto = "*" +bcrypt = "*" +blinker = "*" +cached-property = "*" +cairocffi = "*" +certifi = "*" +cffi = "*" +chardet = "*" +click = "*" +coverage = "*" +cryptography = "*" +cssselect2 = "*" +defusedxml = "*" +dominate = "*" +elasticsearch = "*" +holidays = "*" +html5lib = "*" +idna = "*" +isodate = "*" +itsdangerous = "*" +jsonschema = "*" +ldap3 = "*" +lxml = "*" +oauthlib = "*" +paramiko = "*" +pdfrw = "*" +pkgconfig = "*" +pyasn1 = "*" +pycparser = "*" +pyparsing = "*" +python-dateutil = "*" +python-dotenv = "*" +python-editor = "*" +pytz = "*" +raven = "*" +redis = "*" +requests = "*" +requests-oauthlib = "*" +simplekv = "*" +six = "*" +tablib = "*" +tinycss2 = "*" +tzlocal = "*" +urllib3 = "*" +virtualenv = "*" +visitor = "*" +webencodings = "*" +xmlsec = "*" +business_calendar = "*" +CairoSVG = "*" +Flask = "*" +Flask-Bootstrap = "*" +Flask-Elasticsearch = "*" +Flask-Login = "*" +Flask-Mail = "*" +Flask-Migrate = "*" +Flask-Moment = "*" +Flask-reCaptcha = "*" +Flask-Script = "*" +Flask-SQLAlchemy = "*" +Flask-Tracy = "*" +Flask-WeasyPrint = "*" +Flask-WTF = "*" +Jinja2 = "*" +Mako = "*" +MarkupSafe = "*" +Pillow = "*" +PyNaCl = "*" +pyOpenSSL = "*" +Pyphen = "*" +SQLAlchemy = "*" +WeasyPrint = "*" +Werkzeug = "*" +WTForms = {extras = ["email"], version = "*"} psycopg2-binary = "*" -python-magic-bin = "==0.4.14" +python-magic-bin = "*" flask-session = "*" celery = "*" billiard = "*" @@ -115,4 +115,4 @@ amqp = "*" pyotp = "*" [requires] -python_version = "3.7" +python_version = "3.8.3" diff --git a/Pipfile.lock b/Pipfile.lock index 386599c70..1044774ac 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "66a3c54044861c8659527d586b8ad86596daddaa22d1190356155cc101658da0" + "sha256": "375fb8ea75ec886543bc6067e7d8e661a06c4ba6c4c23c98746403b294328d80" }, "pipfile-spec": 6, "requires": { - "python_version": "3.7" + "python_version": "3.8.3" }, "sources": [ { @@ -18,10 +18,11 @@ "default": { "alembic": { "hashes": [ - "sha256:16505782b229007ae905ef9e0ae6e880fddafa406f086ac7d442c1aaf712f8c2" + "sha256:4e02ed2aa796bd179965041afa092c55b51fb077de19d61835673cc80672c01c", + "sha256:5334f32314fb2a56d86b4c4dd1ae34b08c03cae4cb888bc699942104d66bc245" ], "index": "pypi", - "version": "==1.0.7" + "version": "==1.4.3" }, "amqp": { "hashes": [ @@ -40,51 +41,40 @@ }, "appdirs": { "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" ], "index": "pypi", - "version": "==1.4.3" + "version": "==1.4.4" }, "asn1crypto": { "hashes": [ - "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", - "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8", + "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c" ], "index": "pypi", - "version": "==0.24.0" + "version": "==1.4.0" }, - "backports.csv": { + "attrs": { "hashes": [ - "sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d", - "sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==1.0.7" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" }, "bcrypt": { "hashes": [ - "sha256:0ba875eb67b011add6d8c5b76afbd92166e98b1f1efab9433d5dc0fafc76e203", - "sha256:21ed446054c93e209434148ef0b362432bb82bbdaf7beef70a32c221f3e33d1c", - "sha256:28a0459381a8021f57230954b9e9a65bb5e3d569d2c253c5cac6cb181d71cf23", - "sha256:2aed3091eb6f51c26b7c2fad08d6620d1c35839e7a362f706015b41bd991125e", - "sha256:2fa5d1e438958ea90eaedbf8082c2ceb1a684b4f6c75a3800c6ec1e18ebef96f", - "sha256:3a73f45484e9874252002793518da060fb11eaa76c30713faa12115db17d1430", - "sha256:3e489787638a36bb466cd66780e15715494b6d6905ffdbaede94440d6d8e7dba", - "sha256:44636759d222baa62806bbceb20e96f75a015a6381690d1bc2eda91c01ec02ea", - "sha256:678c21b2fecaa72a1eded0cf12351b153615520637efcadc09ecf81b871f1596", - "sha256:75460c2c3786977ea9768d6c9d8957ba31b5fbeb0aae67a5c0e96aab4155f18c", - "sha256:8ac06fb3e6aacb0a95b56eba735c0b64df49651c6ceb1ad1cf01ba75070d567f", - "sha256:8fdced50a8b646fff8fa0e4b1c5fd940ecc844b43d1da5a980cb07f2d1b1132f", - "sha256:9b2c5b640a2da533b0ab5f148d87fb9989bf9bcb2e61eea6a729102a6d36aef9", - "sha256:a9083e7fa9adb1a4de5ac15f9097eb15b04e2c8f97618f1b881af40abce382e1", - "sha256:b7e3948b8b1a81c5a99d41da5fb2dc03ddb93b5f96fcd3fd27e643f91efa33e1", - "sha256:b998b8ca979d906085f6a5d84f7b5459e5e94a13fc27c28a3514437013b6c2f6", - "sha256:dd08c50bc6f7be69cd7ba0769acca28c846ec46b7a8ddc2acf4b9ac6f8a7457e", - "sha256:de5badee458544ab8125e63e39afeedfcf3aef6a6e2282ac159c95ae7472d773", - "sha256:ede2a87333d24f55a4a7338a6ccdccf3eaa9bed081d1737e0db4dbd1a4f7e6b6" - ], - "index": "pypi", - "version": "==3.1.6" + "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", + "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", + "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", + "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", + "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" + ], + "index": "pypi", + "version": "==3.2.0" }, "billiard": { "hashes": [ @@ -111,11 +101,11 @@ }, "cached-property": { "hashes": [ - "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", - "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" + "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", + "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" ], "index": "pypi", - "version": "==1.5.1" + "version": "==1.5.2" }, "cachelib": { "hashes": [ @@ -126,88 +116,92 @@ }, "cairocffi": { "hashes": [ - "sha256:15386c3a9e08823d6826c4491eaccc7b7254b1dc587a3b9ce60c350c3f990337" + "sha256:9a979b500c64c8179fec286f337e8fe644eca2f2cd05860ce0b62d25f22ea140" ], "index": "pypi", - "version": "==0.9.0" + "version": "==1.2.0" }, "cairosvg": { "hashes": [ - "sha256:07190b3d473a33695cda4fafee085922141ae87836303013dd4dcdebcba886a9", - "sha256:66f333ef5dc79fdfbd3bbe98adc791b1f854e0461067d202fa7b15de66d517ec" + "sha256:bfa0deea7fa0b9b2f29e41b747a915c249dbca731a4667c2917e47ff96e773e0", + "sha256:f1ff02625520493eafb5695d987f69544555524bb0f95695b9ddd3f9dc7d29d5" ], "index": "pypi", - "version": "==2.3.0" + "version": "==2.5.1" }, "celery": { "hashes": [ - "sha256:2cc87518fdd41375c8f40e2b0b59391568a3fb0a0b5f859f7bedef9b37efbc90", - "sha256:d7bcb0fca6fc94c41c946e8979c4c276a02cc7532c68757b43b25c2fa9eda68b" + "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13", + "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c" ], "index": "pypi", - "version": "==5.0.3" + "version": "==5.0.5" }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" ], "index": "pypi", - "version": "==2018.11.29" + "version": "==2020.12.5" }, "cffi": { "hashes": [ - "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", - "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", - "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", - "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", - "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", - "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", - "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" - ], - "index": "pypi", - "version": "==1.11.5" + "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", + "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", + "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", + "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", + "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", + "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", + "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", + "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", + "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", + "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", + "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", + "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", + "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", + "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", + "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", + "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", + "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", + "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", + "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", + "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", + "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", + "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", + "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", + "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", + "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", + "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", + "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", + "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", + "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", + "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", + "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", + "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", + "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", + "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", + "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", + "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" + ], + "index": "pypi", + "version": "==1.14.4" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], "index": "pypi", - "version": "==3.0.4" + "version": "==4.0.0" }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], "index": "pypi", - "version": "==7.0" + "version": "==7.1.2" }, "click-didyoumean": { "hashes": [ @@ -229,123 +223,156 @@ ], "version": "==0.1.6" }, + "convertdate": { + "hashes": [ + "sha256:9d2b0cd8d5382d2458d4cfa59665abba398a9e9bfd3a01c6f61b7b47768d28bf", + "sha256:fc34133ef6ceb31738cf1169b528ba487d0164d69f4451a7cef206887c45b71d" + ], + "version": "==2.2.0" + }, "coverage": { "hashes": [ - "sha256:06123b58a1410873e22134ca2d88bd36680479fe354955b3579fb8ff150e4d27", - "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", - "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", - "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", - "sha256:0d34245f824cc3140150ab7848d08b7e2ba67ada959d77619c986f2062e1f0e8", - "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", - "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", - "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", - "sha256:258b21c5cafb0c3768861a6df3ab0cfb4d8b495eee5ec660e16f928bf7385390", - "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", - "sha256:3ad59c84c502cd134b0088ca9038d100e8fb5081bbd5ccca4863f3804d81f61d", - "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", - "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", - "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", - "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", - "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", - "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", - "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", - "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", - "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", - "sha256:71afc1f5cd72ab97330126b566bbf4e8661aab7449f08895d21a5d08c6b051ff", - "sha256:7349c27128334f787ae63ab49d90bf6d47c7288c63a0a5dfaa319d4b4541dd2c", - "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", - "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", - "sha256:859714036274a75e6e57c7bab0c47a4602d2a8cfaaa33bbdb68c8359b2ed4f5c", - "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", - "sha256:869ef4a19f6e4c6987e18b315721b8b971f7048e6eaea29c066854242b4e98d9", - "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", - "sha256:977e2d9a646773cc7428cdd9a34b069d6ee254fadfb4d09b3f430e95472f3cf3", - "sha256:99bd767c49c775b79fdcd2eabff405f1063d9d959039c0bdd720527a7738748a", - "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", - "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", - "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", - "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", - "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", - "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", - "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", - "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", - "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", - "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", - "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" - ], - "index": "pypi", - "version": "==4.5.2" + "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297", + "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1", + "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497", + "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606", + "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528", + "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b", + "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4", + "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830", + "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1", + "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f", + "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d", + "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3", + "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8", + "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500", + "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7", + "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb", + "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b", + "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059", + "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b", + "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72", + "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36", + "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277", + "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c", + "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631", + "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff", + "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8", + "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec", + "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b", + "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7", + "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105", + "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b", + "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c", + "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b", + "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98", + "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4", + "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879", + "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f", + "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4", + "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044", + "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e", + "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899", + "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f", + "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448", + "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714", + "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2", + "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d", + "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd", + "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7", + "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae" + ], + "index": "pypi", + "version": "==5.3.1" }, "cryptography": { "hashes": [ - "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", - "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", - "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", - "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", - "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", - "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", - "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", - "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", - "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", - "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", - "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", - "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", - "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", - "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", - "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", - "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", - "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", - "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", - "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" - ], - "index": "pypi", - "version": "==2.5" + "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d", + "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7", + "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901", + "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c", + "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244", + "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6", + "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5", + "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e", + "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c", + "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0", + "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812", + "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a", + "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030", + "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302" + ], + "index": "pypi", + "version": "==3.3.1" }, "cssselect2": { "hashes": [ - "sha256:267eebc7378ade2e8be710cd0179606ad9c95ecc673138fccfcfba42c5ce153d", - "sha256:505d2ce3d3a1d390ddb52f7d0864b7efeb115a5b852a91861b498b92424503ab" + "sha256:2f4a9f20965367bae459e3bb42561f7927e0cfe5b7ea1692757cf67ef5d7dace", + "sha256:93fbb9af860e95dd40bf18c3b2b6ed99189a07c0f29ba76f9c5be71344664ec8" ], "index": "pypi", - "version": "==0.2.1" + "version": "==0.4.1" }, "defusedxml": { "hashes": [ - "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", - "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" + "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", + "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.6.0" + }, + "distlib": { + "hashes": [ + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + ], + "version": "==0.3.1" + }, + "dnspython": { + "hashes": [ + "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7", + "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.0" }, "dominate": { "hashes": [ - "sha256:4076735c0745fe771e57b2313dbb4bfeec42731816ee23cee509f66e8912aa51", - "sha256:4b9fd42d2824b79761799590697db45bf93daad511b130c50513af38da33df9b" + "sha256:76ec2cde23700a6fc4fee098168b9dee43b99c2f1dd0ca6a711f683e8eb7e1e4", + "sha256:84b5f71ed30021193cb0faa45d7776e1083f392cfe67a49f44e98cb2ed76c036" ], "index": "pypi", - "version": "==2.3.5" + "version": "==2.6.0" }, "elasticsearch": { "hashes": [ - "sha256:7546cc08e3899716e12fe67d12d7cfe9a64647014d1134b014c3c392b63cad42", - "sha256:aada5cfdc4a543c47098eb3aca6663848ef5d04b4324935ced441debc11ec98b" + "sha256:4ebd34fd223b31c99d9f3b6b6236d3ac18b3046191a37231e8235b06ae7db955", + "sha256:a725dd923d349ca0652cf95d6ce23d952e2153740cf4ab6daf4a2d804feeed48" ], "index": "pypi", - "version": "==6.3.1" + "version": "==7.10.1" }, - "et-xmlfile": { + "email-validator": { "hashes": [ - "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b" + "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f", + "sha256:1a13bd6050d1db4475f13e444e169b6fe872434922d38968c67cea9568cce2f0" ], - "version": "==1.0.1" + "version": "==1.1.2" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" }, "flask": { "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", - "version": "==1.0.2" + "version": "==1.1.2" }, "flask-bootstrap": { "hashes": [ @@ -363,10 +390,11 @@ }, "flask-login": { "hashes": [ - "sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec" + "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b", + "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0" ], "index": "pypi", - "version": "==0.4.1" + "version": "==0.5.0" }, "flask-mail": { "hashes": [ @@ -377,19 +405,19 @@ }, "flask-migrate": { "hashes": [ - "sha256:0c42c34357b24faac63bc6e7af028c8614235f5210eded53802ced450c5392c2", - "sha256:8356fa6a02694da34e78da1e38cf91c944b219f4bd4b89493a3b261a305994ab" + "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", + "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" ], "index": "pypi", - "version": "==2.3.1" + "version": "==2.5.3" }, "flask-moment": { "hashes": [ - "sha256:71a601fcd5be4742227251641cb706c109680b54c5fb25c5d2ed96e576ec3b4d", - "sha256:af7ccd599d85e751ff1f7661904daa51df9950e9bc9bd4ccf174bd38ccbc401f" + "sha256:75e1ae59b7562731acf9faf295c0bfd8165f51f67a62bd779e0c57e5f1c66dbf", + "sha256:ff4cc0c4f8ec6798e19ba17fac409a8090f21677da6b21e3e1e4450344d8ed71" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.11.0" }, "flask-recaptcha": { "hashes": [ @@ -415,11 +443,11 @@ }, "flask-sqlalchemy": { "hashes": [ - "sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b", - "sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53" + "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e", + "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5" ], "index": "pypi", - "version": "==2.3.2" + "version": "==2.4.4" }, "flask-tracy": { "hashes": [ @@ -431,42 +459,44 @@ }, "flask-weasyprint": { "hashes": [ - "sha256:0dc50b3d62da43a2f650e54555713a72651bcf169ff1503282f2003203c854f1" + "sha256:688424cd47e1b06915feeb295c4a03df667222865676c3029ed52a6be5622caf", + "sha256:ceb48d15dca49d6f32be60a19fb6d6a43f0668978ac8af4240292b85630652b1" ], "index": "pypi", - "version": "==0.5" + "version": "==0.6" }, "flask-wtf": { "hashes": [ - "sha256:5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", - "sha256:d9a9e366b32dcbb98ef17228e76be15702cd2600675668bca23f63a7947fd5ac" + "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", + "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" ], "index": "pypi", - "version": "==0.14.2" + "version": "==0.14.3" }, "holidays": { "hashes": [ - "sha256:b38e11736e2fad04c51e6d8d38ff66b483905d9448ba0291640cda5889687baa", - "sha256:bb91e9502b1621bf5627f04d5f5c82fbf72c492b5bbe36a8fd0c0d463da216da" + "sha256:72378e7c00139ab157adf5155c0ec7af62489b7cae8d66dfc53a9a02ddcb1cab", + "sha256:72c8686b649364013e16ffc216dc616e7e81f6c1fa7ecf2afe342826178b482d", + "sha256:7fb3fe2fd4823f1169b0695bf2b79135422cce10f5aa9c722213aa3f082a16a2" ], "index": "pypi", - "version": "==0.9.9" + "version": "==0.10.4" }, "html5lib": { "hashes": [ - "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", - "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", + "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" ], "index": "pypi", - "version": "==1.0.1" + "version": "==1.1" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], "index": "pypi", - "version": "==2.8" + "version": "==2.10" }, "isodate": { "hashes": [ @@ -484,28 +514,21 @@ "index": "pypi", "version": "==1.1.0" }, - "jdcal": { - "hashes": [ - "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba", - "sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8" - ], - "version": "==1.4.1" - }, "jinja2": { "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], "index": "pypi", - "version": "==2.10" + "version": "==2.11.2" }, "jsonschema": { "hashes": [ - "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", - "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", + "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" ], "index": "pypi", - "version": "==2.6.0" + "version": "==3.2.0" }, "kombu": { "hashes": [ @@ -515,121 +538,129 @@ "index": "pypi", "version": "==5.0.2" }, + "korean-lunar-calendar": { + "hashes": [ + "sha256:12ce54b1392ed45a82dc6cea85ee5f7e33630556e82488f57e37a22482c8275d", + "sha256:a619ea88610129019267467b85cc9faf0fab6e1694b2e782d1aeb610cdd382d5" + ], + "version": "==0.2.1" + }, "ldap3": { "hashes": [ - "sha256:0b70034b0a535b36d1103b37c121f7efa718bb88d0ed9023f121b24b834a9584", - "sha256:3f67c83185b1f0df8fdf6b52fa42c55bc9e9b7120c8b7fec60f0d6003c536d18", - "sha256:6b4139184ef0d160415f94e5cfaa039e5b725ba6986cff49139d35c37d230f00", - "sha256:7a88f3cc2584031e97a2e19f906abb8f655aca80b838d454b1d3c9e103a36c4b", - "sha256:a8cf20c3b51032399cd74a42a0d31dbf0a8bc450598f0972e167ad992a43c9f2", - "sha256:dd9be8ea27773c4ffc18ede0b95c3ca1eb12513a184590b9f8ae423db3f71eb9" + "sha256:10bdd23b612e942ce90ea4dbc744dfd88735949833e46c5467a2dcf68e60f469", + "sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7", + "sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0", + "sha256:8f59a7b5399555b22db06f153daa76c77ded2dd84bc0f0ffe5b0b33901b6eac4", + "sha256:bed71c6ce2f70a00a330eed0c8370664c065239d45bcbe1b82517b6f6eed7f25" ], "index": "pypi", - "version": "==2.5.2" + "version": "==2.8.1" }, "lxml": { "hashes": [ - "sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21", - "sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0", - "sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297", - "sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591", - "sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801", - "sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f", - "sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541", - "sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58", - "sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6", - "sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730", - "sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49", - "sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc", - "sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879", - "sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6", - "sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e", - "sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c", - "sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2", - "sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d", - "sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485", - "sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e", - "sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73", - "sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488", - "sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5", - "sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11", - "sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298", - "sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af" - ], - "index": "pypi", - "version": "==4.3.0" + "sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d", + "sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37", + "sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01", + "sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2", + "sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644", + "sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75", + "sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80", + "sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2", + "sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780", + "sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98", + "sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308", + "sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf", + "sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388", + "sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d", + "sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3", + "sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8", + "sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af", + "sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2", + "sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e", + "sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939", + "sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03", + "sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d", + "sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a", + "sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5", + "sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a", + "sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711", + "sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf", + "sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089", + "sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505", + "sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b", + "sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f", + "sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc", + "sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e", + "sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931", + "sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc", + "sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe", + "sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e" + ], + "index": "pypi", + "version": "==4.6.2" }, "mako": { "hashes": [ - "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" + "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27", + "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9" ], "index": "pypi", - "version": "==1.0.7" + "version": "==1.1.3" }, "markupsafe": { "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "index": "pypi", - "version": "==1.1.0" + "version": "==1.1.1" }, "oauthlib": { "hashes": [ - "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", - "sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e" + "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", + "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], "index": "pypi", - "version": "==3.0.1" - }, - "odfpy": { - "hashes": [ - "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec", - "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0" - ], - "version": "==1.4.1" - }, - "openpyxl": { - "hashes": [ - "sha256:18e11f9a650128a12580a58e3daba14e00a11d9e907c554a17ea016bf1a2c71b", - "sha256:f7d666b569f729257082cf7ddc56262431878f602dcc2bc3980775c59439cdab" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.5" + "version": "==3.1.0" }, "paramiko": { "hashes": [ - "sha256:3c16b2bfb4c0d810b24c40155dbfd113c0521e7e6ee593d704e84b4c658a1f3b", - "sha256:a8975a7df3560c9f1e2b43dc54ebd40fd00a7017392ca5445ce7df409f900fcb" + "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", + "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" ], "index": "pypi", - "version": "==2.4.2" + "version": "==2.7.2" }, "pdfrw": { "hashes": [ @@ -641,76 +672,53 @@ }, "pillow": { "hashes": [ - "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", - "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", - "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", - "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", - "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", - "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", - "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", - "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", - "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", - "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", - "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", - "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", - "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", - "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", - "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", - "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", - "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", - "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", - "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", - "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", - "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", - "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", - "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", - "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", - "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", - "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", - "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", - "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", - "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", - "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", - "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", - "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", - "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", - "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", - "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", - "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", - "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", - "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", - "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", - "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", - "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", - "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", - "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", - "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", - "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", - "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", - "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", - "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", - "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", - "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", - "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" - ], - "index": "pypi", - "version": "==5.4.1" + "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6", + "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865", + "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174", + "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032", + "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a", + "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e", + "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378", + "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17", + "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c", + "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913", + "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7", + "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0", + "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820", + "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba", + "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2", + "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b", + "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9", + "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234", + "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d", + "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5", + "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206", + "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9", + "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8", + "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59", + "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d", + "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a", + "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b", + "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d" + ], + "index": "pypi", + "version": "==8.1.0" }, "pkgconfig": { "hashes": [ - "sha256:048c3b457da7b6f686b647ab10bf09e2250e4c50acfe6f215398a8b5e6fcdb52", - "sha256:3eb03a6345d4916489d3433f60e6d044a21f50e1d86fb611a52fd28061582065" + "sha256:97bfe3d981bab675d5ea3ef259045d7919c93897db7d3b59d4e8593cba8d354f", + "sha256:cddf2d7ecadb272178a942eb852a9dee46bda2adcc36c3416b0fef47a4ed9f38" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.5.1" }, "prompt-toolkit": { "hashes": [ - "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c", - "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63" + "sha256:45b154489d89dc41cce86a069a65f3886206518e7ca93fa9d7dad70546c78d54", + "sha256:c5eeab58dd31b541442825d7870777f2a2f764eb5fda03334d5219cd84b9722f" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.8" + "version": "==3.0.9" }, "psycopg2-binary": { "hashes": [ @@ -755,64 +763,68 @@ }, "pyasn1": { "hashes": [ - "sha256:061442c60842f6d11051d4fdae9bc197b64bd41573a12234a753a0cb80b4f30b", - "sha256:0ee2449bf4c4e535823acc25624c45a8b454f328d59d3f3eeb82d3567100b9bd", - "sha256:5f9fb05c33e53b9a6ee3b1ed1d292043f83df465852bec876e93b47fd2df7eed", - "sha256:65201d28e081f690a32401e6253cca4449ccacc8f3988e811fae66bd822910ee", - "sha256:79b336b073a52fa3c3d8728e78fa56b7d03138ef59f44084de5f39650265b5ff", - "sha256:8ec20f61483764de281e0b4aba7d12716189700debcfa9e7935780850bf527f3", - "sha256:9458d0273f95d035de4c0d5e0643f25daba330582cc71bb554fe6969c015042a", - "sha256:98d97a1833a29ca61cd04a60414def8f02f406d732f9f0bcb49f769faff1b699", - "sha256:b00d7bfb6603517e189d1ad76967c7e805139f63e43096e5f871d1277f50aea5", - "sha256:b06c0cfd708b806ea025426aace45551f91ea7f557e0c2d4fbd9a4b346873ce0", - "sha256:d14d05984581770333731690f5453efd4b82e1e5d824a1d7976b868a2e5c38e8", - "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", - "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" ], "index": "pypi", - "version": "==0.4.5" + "version": "==0.4.8" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], "index": "pypi", - "version": "==2.19" + "version": "==2.20" + }, + "pymeeus": { + "hashes": [ + "sha256:56430209e6f9a039f1ba73f107ab0b23121548e2a67ed2855f2416c3749a5662" + ], + "version": "==0.3.7" }, "pynacl": { "hashes": [ - "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", - "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", - "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", - "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", - "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", - "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", - "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", - "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", - "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", - "sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5", - "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", - "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", - "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", - "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", - "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", - "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", - "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", - "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", - "sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92", - "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", - "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0" + "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", + "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", + "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", + "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", + "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", + "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", + "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", + "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", + "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", + "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", + "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", + "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", + "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", + "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", + "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", + "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", + "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", + "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.4.0" }, "pyopenssl": { "hashes": [ - "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", - "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6" + "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", + "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" ], "index": "pypi", - "version": "==19.0.0" + "version": "==20.0.1" }, "pyotp": { "hashes": [ @@ -824,35 +836,42 @@ }, "pyparsing": { "hashes": [ - "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", - "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], "index": "pypi", - "version": "==2.3.1" + "version": "==2.4.7" }, "pyphen": { "hashes": [ - "sha256:3b633a50873156d777e1f1075ba4d8e96a6ad0a3ca42aa3ea9a6259f93f18921", - "sha256:e172faf10992c8c9d369bdc83e36dbcf1121f4ed0d881f1a0b521935aee583b5" + "sha256:624b036eafe6f38fad3e79ee676ec711a8ce634feb9f34ac615bbaf73e662111", + "sha256:719b21dfb4b04fbc11cc0f6112418535fe35474021120cccfffc43a25fe63128" ], "index": "pypi", - "version": "==0.9.5" + "version": "==0.10.0" + }, + "pyrsistent": { + "hashes": [ + "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" + ], + "markers": "python_version >= '3.5'", + "version": "==0.17.3" }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], "index": "pypi", - "version": "==2.8.0" + "version": "==2.8.1" }, "python-dotenv": { "hashes": [ - "sha256:a84569d0e00d178bc5b957f7ff208bf49287cbf61857c31c258c4a91f571527b", - "sha256:c9b1ddd3cdbe75c7d462cb84674d87130f4b948f090f02c7d7144779afb99ae0" + "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", + "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" ], "index": "pypi", - "version": "==0.10.1" + "version": "==0.15.0" }, "python-editor": { "hashes": [ @@ -876,29 +895,11 @@ }, "pytz": { "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], "index": "pypi", - "version": "==2018.9" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" + "version": "==2019.3" }, "raven": { "hashes": [ @@ -910,83 +911,121 @@ }, "redis": { "hashes": [ - "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", - "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" + "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", + "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" ], "index": "pypi", - "version": "==3.1.0" + "version": "==3.5.3" }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" ], "index": "pypi", - "version": "==2.21.0" + "version": "==2.25.1" }, "requests-oauthlib": { "hashes": [ - "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", - "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140", - "sha256:dd5a0499abfefd087c6dd96693cbd5bfd28aa009719a7f85ab3fabe3956ef19a" + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", + "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" }, "simplekv": { "hashes": [ - "sha256:29c64f4a857f147c2df47281300ba6604f5b5365cd943de32ca5268cc2e5dd68", - "sha256:4a1cfa19a44f51bc71a3b47d32619b801992217f5e12f39c28aaed2dcff5e795", - "sha256:c34608abb2b492c9a06e1c56c6cbb8f88c74ff699490359b9e06874ac58588c2" + "sha256:8953a36cb3741ea821c9de1962b5313bf6fe1b927f6ced2a55266eb8ce2cd0f6", + "sha256:af91a50af41a286a8b7b93292b21dd1af37f38e9513fea0eb4fa75ce778c1683", + "sha256:fcee8d972d092de0dc83732084e389c9b95839503537ef85c1a2eeb07182f2f5" ], "index": "pypi", - "version": "==0.11.11" + "version": "==0.14.1" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], "index": "pypi", - "version": "==1.12.0" + "version": "==1.15.0" }, "sqlalchemy": { "hashes": [ - "sha256:52a42dbf02d0562d6e90e7af59f177f1cc027e72833cc29c3a821eefa009c71d" - ], - "index": "pypi", - "version": "==1.2.17" + "sha256:04f995fcbf54e46cddeb4f75ce9dfc17075d6ae04ac23b2bacb44b3bc6f6bf11", + "sha256:0c6406a78a714a540d980a680b86654feadb81c8d0eecb59f3d6c554a4c69f19", + "sha256:0c72b90988be749e04eff0342dcc98c18a14461eb4b2ad59d611b57b31120f90", + "sha256:108580808803c7732f34798eb4a329d45b04c562ed83ee90f09f6a184a42b766", + "sha256:1418f5e71d6081aa1095a1d6b567a562d2761996710bdce9b6e6ba20a03d0864", + "sha256:17610d573e698bf395afbbff946544fbce7c5f4ee77b5bcb1f821b36345fae7a", + "sha256:216ba5b4299c95ed179b58f298bda885a476b16288ab7243e89f29f6aeced7e0", + "sha256:2ff132a379838b1abf83c065be54cef32b47c987aedd06b82fc76476c85225eb", + "sha256:314f5042c0b047438e19401d5f29757a511cfc2f0c40d28047ca0e4c95eabb5b", + "sha256:318b5b727e00662e5fc4b4cd2bf58a5116d7c1b4dd56ffaa7d68f43458a8d1ed", + "sha256:3ab5b44a07b8c562c6dcb7433c6a6c6e03266d19d64f87b3333eda34e3b9936b", + "sha256:426ece890153ccc52cc5151a1a0ed540a5a7825414139bb4c95a868d8da54a52", + "sha256:491fe48adc07d13e020a8b07ef82eefc227003a046809c121bea81d3dbf1832d", + "sha256:4a84c7c7658dd22a33dab2e2aa2d17c18cb004a42388246f2e87cb4085ef2811", + "sha256:54da615e5b92c339e339fe8536cce99fe823b6ed505d4ea344852aefa1c205fb", + "sha256:5a7f224cdb7233182cec2a45d4c633951268d6a9bcedac37abbf79dd07012aea", + "sha256:61628715931f4962e0cdb2a7c87ff39eea320d2aa96bd471a3c293d146f90394", + "sha256:62285607a5264d1f91590abd874d6a498e229d5840669bd7d9f654cfaa599bd0", + "sha256:62fb881ba51dbacba9af9b779211cf9acff3442d4f2993142015b22b3cd1f92a", + "sha256:68428818cf80c60dc04aa0f38da20ad39b28aba4d4d199f949e7d6e04444ea86", + "sha256:6aaa13ee40c4552d5f3a59f543f0db6e31712cc4009ec7385407be4627259d41", + "sha256:70121f0ae48b25ef3e56e477b88cd0b0af0e1f3a53b5554071aa6a93ef378a03", + "sha256:715b34578cc740b743361f7c3e5f584b04b0f1344f45afc4e87fbac4802eb0a0", + "sha256:758fc8c4d6c0336e617f9f6919f9daea3ab6bb9b07005eda9a1a682e24a6cacc", + "sha256:7d4b8de6bb0bc736161cb0bbd95366b11b3eb24dd6b814a143d8375e75af9990", + "sha256:81d8d099a49f83111cce55ec03cc87eef45eec0d90f9842b4fc674f860b857b0", + "sha256:888d5b4b5aeed0d3449de93ea80173653e939e916cc95fe8527079e50235c1d2", + "sha256:95bde07d19c146d608bccb9b16e144ec8f139bcfe7fd72331858698a71c9b4f5", + "sha256:9bf572e4f5aa23f88dd902f10bb103cb5979022a38eec684bfa6d61851173fec", + "sha256:bab5a1e15b9466a25c96cda19139f3beb3e669794373b9ce28c4cf158c6e841d", + "sha256:bd4b1af45fd322dcd1fb2a9195b4f93f570d1a5902a842e3e6051385fac88f9c", + "sha256:bde677047305fe76c7ee3e4492b545e0018918e44141cc154fe39e124e433991", + "sha256:c389d7cc2b821853fb018c85457da3e7941db64f4387720a329bc7ff06a27963", + "sha256:d055ff750fcab69ca4e57b656d9c6ad33682e9b8d564f2fbe667ab95c63591b0", + "sha256:d53f59744b01f1440a1b0973ed2c3a7de204135c593299ee997828aad5191693", + "sha256:f115150cc4361dd46153302a640c7fa1804ac207f9cc356228248e351a8b4676", + "sha256:f1e88b30da8163215eab643962ae9d9252e47b4ea53404f2c4f10f24e70ddc62", + "sha256:f8191fef303025879e6c3548ecd8a95aafc0728c764ab72ec51a0bdf0c91a341" + ], + "index": "pypi", + "version": "==1.3.22" }, "tablib": { "hashes": [ - "sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670", - "sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13" + "sha256:41aa40981cddd7ec4d1fabeae7c38d271601b306386bd05b5c3bcae13e5aeb20", + "sha256:f83cac08454f225a34a305daa20e2110d5e6335135d505f93bc66583a5f9c10d" ], "index": "pypi", - "version": "==0.13.0" + "version": "==3.0.0" }, "tinycss2": { "hashes": [ - "sha256:5e881eaa263bf4dc5c050d43cd6d2203ade1e3a3cda61f5511cf878972e83b78", - "sha256:7c53c2c0e914c7711c295b3101bcc78e0b7eda23ff20228a936efe11cdcc7136" + "sha256:0353b5234bcaee7b1ac7ca3dea7e02cd338a9f8dcbb8f2dcd32a5795ec1e5f9a", + "sha256:fbdcac3044d60eb85fdb2aa840ece43cf7dbe798e373e6ee0be545d4d134e18a" ], "index": "pypi", - "version": "==0.6.1" + "version": "==1.1.0" }, "tzlocal": { "hashes": [ - "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e" + "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", + "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4" ], "index": "pypi", - "version": "==1.5.1" + "version": "==2.1" }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", + "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" ], "index": "pypi", - "version": "==1.24.1" + "version": "==1.26.2" }, "vine": { "hashes": [ @@ -998,11 +1037,11 @@ }, "virtualenv": { "hashes": [ - "sha256:aabc8ef18cddbd8a2a9c7f92bc43e2fea54b1147330d65db920ef3ce9812e3dc", - "sha256:f74a5943a9005c1e897bcf87f7e8af7e00c06fa9777b9ad691f69f9fa54a87d8" + "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", + "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" ], "index": "pypi", - "version": "==13.1.2" + "version": "==20.2.2" }, "visitor": { "hashes": [ @@ -1020,11 +1059,11 @@ }, "weasyprint": { "hashes": [ - "sha256:0265efe2a70cc9c972407f4f868fe59b0876ca361b3da609262f05d3c5156e11", - "sha256:8aefc7704f6a0aa78ffc238d3324f64a7a493c8462ccedb23bf7d0744d9a6ad7" + "sha256:21a1a9f11650ed14241817bf333a0ae0a42f6ae38cd7c2654845cb17352b7434", + "sha256:36b897d219e1944a1a0c97193a121fc175db326c660c50d937e874a995d9c716" ], "index": "pypi", - "version": "==45" + "version": "==52.2" }, "webencodings": { "hashes": [ @@ -1036,41 +1075,41 @@ }, "werkzeug": { "hashes": [ - "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", - "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], "index": "pypi", - "version": "==0.14.1" + "version": "==1.0.1" }, "wtforms": { - "hashes": [ - "sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", - "sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" + "extras": [ + "email" ], - "index": "pypi", - "version": "==2.2.1" - }, - "xlrd": { - "hashes": [ - "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2", - "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.2.0" - }, - "xlwt": { "hashes": [ - "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e", - "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88" + "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c", + "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c" ], - "version": "==1.3.0" + "index": "pypi", + "version": "==2.3.3" }, "xmlsec": { "hashes": [ - "sha256:e573c0172174973223d874ffd158ecd4e0faa761015474385289a6468dd29ed6" - ], - "index": "pypi", - "version": "==1.3.3" + "sha256:252f79ed4482d6eefcca62c3bfc99b8d95c07abd846262d854a207ec4d67fac5", + "sha256:31884dc97cc34cf1681a0f239f613969e61f9a01f4c2d2a62e53d68216fe42d6", + "sha256:32a669dfe447bccecdb4ef79221c0452ce6dad919f3a75daf512792141a54dac", + "sha256:3d13d7b6cb921dbc4d60d00ad00081a038df73a1e69f5bcc3695deb1bf2093b0", + "sha256:5e2f263a21fd146859911479ec35e40a57f519e650f56c775f91367d2a1b6e15", + "sha256:61076be98da4c7cf842a78aa3f129a5039f2ba4992e02480eefe78028d317698", + "sha256:69d7f965d6b74b3266f7baa99a0377d9c76acbf26c615b4ee8d2cbe17bf85528", + "sha256:6d8bb24c3a4db398011f394e29b58cd34c9c26d76b772c5d418d8579df127234", + "sha256:6d9d46d1f6b4985023469a1e334cb35c7c8fc6bd9d8b65ca52b923a7a6869c2a", + "sha256:8a7ffdc4f7f760253aa4dd8d2037358eb33915ca1dcf1c2422b19fcf0ab68506", + "sha256:927fc5755bb93dc09275bd5d818811e016290c194012d63f8e6f86b7ece3e468", + "sha256:dcaa084c3700f775eba09d81a1432444f82d9ad6270320c56c1a733d71cceb3a", + "sha256:f59698cc0366395ca79b48b080674973541aae290670c57d88f05d939a4c00da" + ], + "index": "pypi", + "version": "==1.3.9" } }, "develop": { @@ -1084,98 +1123,119 @@ }, "atomicwrites": { "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", + "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.4.0" }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "index": "pypi", - "version": "==18.2.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" + }, + "backcall": { + "hashes": [ + "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", + "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" + ], + "version": "==0.2.0" }, "coverage": { "hashes": [ - "sha256:06123b58a1410873e22134ca2d88bd36680479fe354955b3579fb8ff150e4d27", - "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", - "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", - "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", - "sha256:0d34245f824cc3140150ab7848d08b7e2ba67ada959d77619c986f2062e1f0e8", - "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", - "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", - "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", - "sha256:258b21c5cafb0c3768861a6df3ab0cfb4d8b495eee5ec660e16f928bf7385390", - "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", - "sha256:3ad59c84c502cd134b0088ca9038d100e8fb5081bbd5ccca4863f3804d81f61d", - "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", - "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", - "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", - "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", - "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", - "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", - "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", - "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", - "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", - "sha256:71afc1f5cd72ab97330126b566bbf4e8661aab7449f08895d21a5d08c6b051ff", - "sha256:7349c27128334f787ae63ab49d90bf6d47c7288c63a0a5dfaa319d4b4541dd2c", - "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", - "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", - "sha256:859714036274a75e6e57c7bab0c47a4602d2a8cfaaa33bbdb68c8359b2ed4f5c", - "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", - "sha256:869ef4a19f6e4c6987e18b315721b8b971f7048e6eaea29c066854242b4e98d9", - "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", - "sha256:977e2d9a646773cc7428cdd9a34b069d6ee254fadfb4d09b3f430e95472f3cf3", - "sha256:99bd767c49c775b79fdcd2eabff405f1063d9d959039c0bdd720527a7738748a", - "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", - "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", - "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", - "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", - "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", - "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", - "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", - "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", - "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", - "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", - "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" - ], - "index": "pypi", - "version": "==4.5.2" + "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297", + "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1", + "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497", + "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606", + "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528", + "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b", + "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4", + "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830", + "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1", + "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f", + "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d", + "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3", + "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8", + "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500", + "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7", + "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb", + "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b", + "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059", + "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b", + "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72", + "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36", + "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277", + "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c", + "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631", + "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff", + "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8", + "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec", + "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b", + "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7", + "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105", + "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b", + "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c", + "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b", + "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98", + "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4", + "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879", + "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f", + "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4", + "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044", + "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e", + "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899", + "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f", + "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448", + "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714", + "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2", + "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d", + "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd", + "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7", + "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae" + ], + "index": "pypi", + "version": "==5.3.1" }, "decorator": { "hashes": [ - "sha256:73cbaadb8bc4e3c65fe1100773d56331a2d756cc0f5c7b9d8d5d5223fe04f600", - "sha256:953d6bf082b100f43229cf547f4f97f97e970f5ad645ee7601d55ff87afdfe76" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], "index": "pypi", - "version": "==4.0.11" + "version": "==4.4.2" }, "faker": { "hashes": [ - "sha256:55f3d00af647a8fc31c7a07b74b7b1d82cb7176fed758e1dc94d3bf0bdb802ba", - "sha256:84e0c48461498dbd5b22bfb2885a200eb93467dc845ba4800354713dde300700" + "sha256:7b0c4bb678be21a68640007f254259c73d18f7996a3448267716423360519732", + "sha256:7e98483fc273ec5cfe1c9efa9b99adaa2de4c6b610fbc62d3767088e4974b0ce" ], "index": "pypi", - "version": "==0.7.9" + "version": "==5.3.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" }, "ipdb": { "hashes": [ - "sha256:fffc45b615e46eb75becbd88a30c69c75e7164ecd0122f2c78579b4dfa41b8c9" + "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334" ], "index": "pypi", - "version": "==0.10.2" + "version": "==0.13.4" }, "ipython": { "hashes": [ - "sha256:54d2ec3ba66ae2e0a34b2b48c85c4818d8ca040edd36ffe81f1e460dd5e03ae5", - "sha256:bf5e615e7d96dac5a61fbf98d9e2926d98aa55582681bea7e9382992a3f43c1d", - "sha256:fb342e89069c9ef176063b14888d7fe774253762f1eda19f09332f1440e6f3c7" + "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f", + "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a" ], "index": "pypi", - "version": "==5.3.0" + "version": "==7.19.0" }, "ipython-genutils": { "hashes": [ @@ -1185,103 +1245,134 @@ "index": "pypi", "version": "==0.2.0" }, + "jedi": { + "hashes": [ + "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93", + "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707" + ], + "markers": "python_version >= '3.6'", + "version": "==0.18.0" + }, "more-itertools": { "hashes": [ - "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", - "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", - "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330", + "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf" ], "index": "pypi", - "version": "==5.0.0" + "version": "==8.6.0" + }, + "packaging": { + "hashes": [ + "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858", + "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.8" + }, + "parso": { + "hashes": [ + "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410", + "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.8.1" }, "pexpect": { "hashes": [ - "sha256:3d132465a75b57aa818341c6521392a06cc660feb3988d7f1074f39bd23c9a92", - "sha256:f853b52afaf3b064d29854771e2db509ef80392509bde2dd7a6ecf2dfc3f0018" + "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", + "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" ], "index": "pypi", - "version": "==4.2.1" + "version": "==4.8.0" }, "pickleshare": { "hashes": [ - "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", - "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" + "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", + "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" ], "index": "pypi", - "version": "==0.7.4" + "version": "==0.7.5" }, "pipdeptree": { "hashes": [ - "sha256:2a1c0d51069c3a573ea2b00c445c8db8ac7dc13c584d05415d6e8121c808af2d", - "sha256:64ac3e1d6b4d8d96620fc44efd44043eef72bb471cd4433c5e5ad8f3e23d44d5" + "sha256:0e4cbe3871287013162dbc0df8ae96fa748d4862160c932cbcbfd73260ff322a", + "sha256:44de04e0034b7d80a5071325c80c4c8f126da001225157f542f62afa79c60f8c", + "sha256:6899ba160bc7db98f0124d1aa6a680aa578adbac8558177ae66dd81bf69369de" ], "index": "pypi", - "version": "==0.9.0" + "version": "==2.0.0" }, "pluggy": { "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", - "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", - "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.13.1" }, "prompt-toolkit": { "hashes": [ - "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c", - "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63" + "sha256:45b154489d89dc41cce86a069a65f3886206518e7ca93fa9d7dad70546c78d54", + "sha256:c5eeab58dd31b541442825d7870777f2a2f764eb5fda03334d5219cd84b9722f" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.8" + "version": "==3.0.9" }, "ptyprocess": { "hashes": [ - "sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6", - "sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae" + "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", + "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.7.0" }, "py": { "hashes": [ - "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", - "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], "index": "pypi", - "version": "==1.7.0" + "version": "==1.10.0" }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716", + "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.7.3" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "index": "pypi", + "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:39555d023af3200d004d09e51b4dd9fdd828baa863cded3fd6ba2f29f757ae2d", - "sha256:c76e93f3145a44812955e8d46cdd302d8a45fbfc7bf22be24fe231f9d8d8853a" + "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8", + "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306" ], "index": "pypi", - "version": "==3.6.0" + "version": "==6.2.1" }, "pytest-cov": { "hashes": [ - "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d", - "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec" + "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191", + "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e" ], "index": "pypi", - "version": "==2.5.1" + "version": "==2.10.1" }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], "index": "pypi", - "version": "==2.8.0" + "version": "==2.8.1" }, "simplegeneric": { "hashes": [ @@ -1292,19 +1383,34 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], "index": "pypi", - "version": "==1.12.0" + "version": "==1.15.0" + }, + "text-unidecode": { + "hashes": [ + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + ], + "version": "==1.3" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" }, "traitlets": { "hashes": [ - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", + "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" ], "index": "pypi", - "version": "==4.3.2" + "version": "==5.0.5" }, "wcwidth": { "hashes": [ diff --git a/openrecords.py b/openrecords.py index 2c99d782a..9332855fb 100644 --- a/openrecords.py +++ b/openrecords.py @@ -54,7 +54,7 @@ from flask import url_for from flask.cli import main from flask_migrate import Migrate, upgrade -from werkzeug.contrib.profiler import ProfilerMiddleware +from werkzeug.middleware.profiler import ProfilerMiddleware from app import create_app, db from app.models import ( From 81a06987762d7a7997632653dcbed925303507e0 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 8 Jan 2021 10:55:17 -0500 Subject: [PATCH 11/44] Add button to MFA manage page and add language to MFA register and verify pages --- .env.example | 1 + app/templates/auth/manage_account.html | 4 ++++ app/templates/mfa/register.html | 6 ++++++ app/templates/mfa/verify.html | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/.env.example b/.env.example index 93a21e96c..192ed896a 100644 --- a/.env.example +++ b/.env.example @@ -71,6 +71,7 @@ SFTP_UPLOAD_DIRECTORY= # Authentication Settings PERMANENT_SESSION_LIFETIME=30 +MFA_ENCRYPT_FILE= # SAML USE_SAML= diff --git a/app/templates/auth/manage_account.html b/app/templates/auth/manage_account.html index 53be75357..af06784eb 100644 --- a/app/templates/auth/manage_account.html +++ b/app/templates/auth/manage_account.html @@ -25,6 +25,10 @@

Manage OpenRecords Account

+
+ Manage MFA Devices +
+ {{ form.csrf_token }} {% if is_agency %} diff --git a/app/templates/mfa/register.html b/app/templates/mfa/register.html index 4fe26a386..f926693c0 100644 --- a/app/templates/mfa/register.html +++ b/app/templates/mfa/register.html @@ -6,6 +6,12 @@

Register MFA Device

+
+

+ Open the authentication app on your device to link it to your OpenRecords account. When the app prompts + you, scan the QR code below with your camera. Enter the name of your device in the field and Submit. +

+
diff --git a/app/templates/mfa/verify.html b/app/templates/mfa/verify.html index fbaddd33c..a76b4e9df 100644 --- a/app/templates/mfa/verify.html +++ b/app/templates/mfa/verify.html @@ -7,6 +7,10 @@

Verify MFA

+

+ Choose the device to authenticate with. Check the authentication app on the device and enter the + authentication code below to log in to OpenRecords. +

{{ form.csrf_token }} {{ form.device.label }} {{ form.device }} From f4f3c670601380431ce99160deac5ac770a0a5b5 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 8 Jan 2021 12:08:16 -0500 Subject: [PATCH 12/44] Update Pipfile --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index aefa490e8..6bde5c20a 100644 --- a/Pipfile +++ b/Pipfile @@ -105,7 +105,7 @@ WeasyPrint = "*" Werkzeug = "*" WTForms = {extras = ["email"], version = "*"} psycopg2-binary = "*" -python-magic-bin = "*" +python-magic-bin = {​​​​version = "*", sys_platform = "=='darwin'"} flask-session = "*" celery = "*" billiard = "*" From f934244ee3d76682fde110567c9c061b0f22301b Mon Sep 17 00:00:00 2001 From: Joel Castillo Date: Mon, 11 Jan 2021 10:01:14 -0500 Subject: [PATCH 13/44] Gunicorn in pipfile; update gunicorn_config.py --- Pipfile | 4 ++- Pipfile.lock | 61 ++++++++++++++++++++++++---------------------- gunicorn_config.py | 21 +++++++--------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/Pipfile b/Pipfile index 6bde5c20a..13e71ef46 100644 --- a/Pipfile +++ b/Pipfile @@ -105,7 +105,7 @@ WeasyPrint = "*" Werkzeug = "*" WTForms = {extras = ["email"], version = "*"} psycopg2-binary = "*" -python-magic-bin = {​​​​version = "*", sys_platform = "=='darwin'"} +python-magic-bin = {version = "*", sys_platform = "=='darwin'"} flask-session = "*" celery = "*" billiard = "*" @@ -113,6 +113,8 @@ kombu = "*" vine = "*" amqp = "*" pyotp = "*" +python-magic = "*" +gunicorn = "*" [requires] python_version = "3.8.3" diff --git a/Pipfile.lock b/Pipfile.lock index 1044774ac..84cd60dc9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "375fb8ea75ec886543bc6067e7d8e661a06c4ba6c4c23c98746403b294328d80" + "sha256": "377256ab30a0c7f87f8400719be981b23a8130c9756a87c637255ef193990a6e" }, "pipfile-spec": 6, "requires": { @@ -330,11 +330,11 @@ }, "dnspython": { "hashes": [ - "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7", - "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d" + "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", + "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" ], "markers": "python_version >= '3.6'", - "version": "==2.0.0" + "version": "==2.1.0" }, "dominate": { "hashes": [ @@ -473,6 +473,14 @@ "index": "pypi", "version": "==0.14.3" }, + "gunicorn": { + "hashes": [ + "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", + "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + ], + "index": "pypi", + "version": "==20.0.4" + }, "holidays": { "hashes": [ "sha256:72378e7c00139ab157adf5155c0ec7af62489b7cae8d66dfc53a9a02ddcb1cab", @@ -714,11 +722,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:45b154489d89dc41cce86a069a65f3886206518e7ca93fa9d7dad70546c78d54", - "sha256:c5eeab58dd31b541442825d7870777f2a2f764eb5fda03334d5219cd84b9722f" + "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7", + "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.9" + "version": "==3.0.10" }, "psycopg2-binary": { "hashes": [ @@ -884,14 +892,17 @@ "index": "pypi", "version": "==1.0.4" }, - "python-magic-bin": { + "python-magic": { "hashes": [ - "sha256:34a788c03adde7608028203e2dbb208f1f62225ad91518787ae26d603ae68892", - "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4", - "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69" + "sha256:356efa93c8899047d1eb7d3eb91e871ba2f5b1376edbaf4cc305e3c872207355", + "sha256:b757db2a5289ea3f1ced9e60f072965243ea43a2221430048fd8cacab17be0ce" ], "index": "pypi", - "version": "==0.4.14" + "version": "==0.4.18" + }, + "python-magic-bin": { + "sys_platform": "=='darwin'", + "version": "*" }, "pytz": { "hashes": [ @@ -1037,11 +1048,11 @@ }, "virtualenv": { "hashes": [ - "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", - "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" + "sha256:205a7577275dd0d9223c730dd498e21a8910600085c3dee97412b041fc4b853b", + "sha256:7992b8de87e544a4ab55afc2240bf8388c4e3b5765d03784dad384bfdf9097ee" ], "index": "pypi", - "version": "==20.2.2" + "version": "==20.3.0" }, "visitor": { "hashes": [ @@ -1113,14 +1124,6 @@ } }, "develop": { - "appnope": { - "hashes": [ - "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442", - "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.2" - }, "atomicwrites": { "hashes": [ "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", @@ -1209,11 +1212,11 @@ }, "faker": { "hashes": [ - "sha256:7b0c4bb678be21a68640007f254259c73d18f7996a3448267716423360519732", - "sha256:7e98483fc273ec5cfe1c9efa9b99adaa2de4c6b610fbc62d3767088e4974b0ce" + "sha256:bd56ab94b9ae45df3cdb6ef3ca3c2be2617e6a640699deb74394143d220faf04", + "sha256:d70983d5e623976e9e8987e9a39e8d55378e66049f393db35ea9a37b3190085f" ], "index": "pypi", - "version": "==5.3.0" + "version": "==5.4.0" }, "iniconfig": { "hashes": [ @@ -1312,11 +1315,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:45b154489d89dc41cce86a069a65f3886206518e7ca93fa9d7dad70546c78d54", - "sha256:c5eeab58dd31b541442825d7870777f2a2f764eb5fda03334d5219cd84b9722f" + "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7", + "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.9" + "version": "==3.0.10" }, "ptyprocess": { "hashes": [ diff --git a/gunicorn_config.py b/gunicorn_config.py index 7b1a45513..62c6d6f1d 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -1,15 +1,11 @@ from multiprocessing import cpu_count -from socket import gethostbyname, gethostname -import os - -# gunicorn -w 16 -b 157.188.77.67:8443 --certfile=/export/local/openrecords/ssl/cert.pem --keyfile=/export/local/openrecords/ssl/key.pem manage:app --preload # bind - The socket to bind. # # A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. # An IP is a valid HOST. -bind = '{ip}:8443'.format(ip=gethostbyname(gethostname())) +bind = "127.0.0.1:8080" # # Worker processes @@ -53,11 +49,11 @@ # A string of "debug", "info", "warning", "error", "critical" # -errorlog = '-' -loglevel = 'info' -accesslog = '-' +errorlog = "-" +loglevel = "info" +accesslog = "-" access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' -logconfig = gunicorn_logging.conf +# logconfig = gunicorn_logging.conf # # Server Mechanics @@ -71,7 +67,8 @@ # A path string. If not set, no PID file will be written. preload = True -pidfile = os.path.join(os.environ.get('HOME'), 'openrecords_gunicorn.pid') +preload_app = True +pidfile = "openrecords_gunicorn.pid" # # SSL @@ -83,5 +80,5 @@ # A path string. # -keyfile = os.path.join(os.environ.get('HOME'), 'ssl', 'key.pem') -certfile = os.path.join(os.environ.get('HOME'), 'ssl', 'cert.pem') +# keyfile = os.path.join(os.environ.get('HOME'), 'ssl', 'key.pem') +# certfile = os.path.join(os.environ.get('HOME'), 'ssl', 'cert.pem') From ac7b2865a40575d60a6d63c6bbde705e571a25fa Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 15 Jan 2021 14:42:30 -0500 Subject: [PATCH 14/44] Fix bugs in mfa register and remove --- app/mfa/views.py | 4 ++-- app/models.py | 3 ++- gunicorn_config.py | 6 ------ ...99f0080036a_add_mfa.py => 84a8fa98bdf2_add_mfa.py} | 11 ++++++----- 4 files changed, 10 insertions(+), 14 deletions(-) rename migrations/versions/{199f0080036a_add_mfa.py => 84a8fa98bdf2_add_mfa.py} (79%) diff --git a/app/mfa/views.py b/app/mfa/views.py index 94b4933aa..46971ab56 100644 --- a/app/mfa/views.py +++ b/app/mfa/views.py @@ -20,7 +20,7 @@ def register(): if request.method == 'POST': if form.validate_on_submit(): device_name = form.device_name.data - secret = form.mfa_secret.data.encode() + secret = form.mfa_secret.data create_object( MFA( @@ -100,7 +100,7 @@ def remove(): update_object( {'is_valid': False}, MFA, - current_user.guid + mfa.id ) create_object( Events( diff --git a/app/models.py b/app/models.py index 8dcd9f181..0b9dbc192 100644 --- a/app/models.py +++ b/app/models.py @@ -2052,7 +2052,8 @@ def populate(cls, json_name=None): class MFA(db.Model): __tablename__ = "mfa" - user_guid = db.Column(db.String(64), db.ForeignKey("users.guid"), primary_key=True) + id = db.Column(db.Integer, primary_key=True) + user_guid = db.Column(db.String(64), db.ForeignKey("users.guid")) secret = db.Column(db.LargeBinary(), nullable=False) device_name = db.Column(db.String(32), nullable=False) is_valid = db.Column(db.Boolean(), nullable=False, default=False) diff --git a/gunicorn_config.py b/gunicorn_config.py index 62c6d6f1d..803f5e040 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -58,16 +58,10 @@ # # Server Mechanics # -# preload - Load application code before the worker processes are forked. -# -# A boolean value. Default value is False. -# # pidfile - A filename to use for the PID file. # # A path string. If not set, no PID file will be written. -preload = True -preload_app = True pidfile = "openrecords_gunicorn.pid" # diff --git a/migrations/versions/199f0080036a_add_mfa.py b/migrations/versions/84a8fa98bdf2_add_mfa.py similarity index 79% rename from migrations/versions/199f0080036a_add_mfa.py rename to migrations/versions/84a8fa98bdf2_add_mfa.py index 850df9b7f..ff4c5355a 100644 --- a/migrations/versions/199f0080036a_add_mfa.py +++ b/migrations/versions/84a8fa98bdf2_add_mfa.py @@ -1,13 +1,13 @@ """Add MFA -Revision ID: 199f0080036a +Revision ID: 84a8fa98bdf2 Revises: afde33bde2e0 -Create Date: 2021-01-04 16:09:59.139962 +Create Date: 2021-01-15 14:19:41.812334 """ # revision identifiers, used by Alembic. -revision = '199f0080036a' +revision = '84a8fa98bdf2' down_revision = 'afde33bde2e0' from alembic import op @@ -17,12 +17,13 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('mfa', - sa.Column('user_guid', sa.String(length=64), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_guid', sa.String(length=64), nullable=True), sa.Column('secret', sa.LargeBinary(), nullable=False), sa.Column('device_name', sa.String(length=32), nullable=False), sa.Column('is_valid', sa.Boolean(), nullable=False), sa.ForeignKeyConstraint(['user_guid'], ['users.guid'], onupdate='CASCADE'), - sa.PrimaryKeyConstraint('user_guid') + sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### From fa1053503c30505e62b4971f79a3b21b88f3d491 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 15 Jan 2021 15:24:01 -0500 Subject: [PATCH 15/44] Update celery_config and .env.example --- .env.example | 8 ++++++-- app/celery_config.py | 14 +++++++++++--- config.py | 3 ++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 192ed896a..df6223c33 100644 --- a/.env.example +++ b/.env.example @@ -59,6 +59,9 @@ ELASTICSEARCH_VERIFY_CERTS=FALSE REDIS_HOST= REDIS_PORT= +# Celery +CELERY_CLEAR_EXPIRED_SESSION_IDS= + # SFTP USE_SFTP=True SFTP_HOSTNAME= @@ -68,9 +71,10 @@ SFTP_PASSWORD= SFTP_RSA_KEY_FILE= SFTP_UPLOAD_DIRECTORY= - # Authentication Settings -PERMANENT_SESSION_LIFETIME=30 +PERMANENT_SESSION_LIFETIME=20 + +# MFA MFA_ENCRYPT_FILE= # SAML diff --git a/app/celery_config.py b/app/celery_config.py index c679eed30..990f3ebb1 100644 --- a/app/celery_config.py +++ b/app/celery_config.py @@ -1,9 +1,17 @@ +import os + from celery.schedules import crontab +from dotenv import load_dotenv + +basedir = os.path.abspath(os.path.dirname(__file__)) +dotenv_path = os.path.join(basedir, '.env') +load_dotenv(dotenv_path) CELERY_IMPORTS = ['app.jobs'] CELERY_TASK_RESULT_EXPIRES = 30 CELERY_TIMEZONE = 'EST' +CELERY_CLEAR_EXPIRED_SESSION_IDS = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS') CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] @@ -12,16 +20,16 @@ 'update_request_statuses': { 'task': 'app.jobs.update_request_statuses', 'schedule': crontab(minute='0', hour='7', day_of_week='1-5') - #'schedule': 60.0 + # 'schedule': 60.0 }, # Every January 1st 'update_next_request_number': { 'task': 'app.jobs.update_next_request_number', 'schedule': crontab(minute='0', hour='0', day_of_month='1', month_of_year='1') }, - # Every 30 minutes + # Every X minutes 'clear_expired_session_ids': { 'task': 'app.jobs.clear_expired_session_ids', - 'schedule': crontab(minute='30') + 'schedule': crontab(minute=CELERY_CLEAR_EXPIRED_SESSION_IDS) } } diff --git a/config.py b/config.py index 11beba071..7b0e3d5e0 100644 --- a/config.py +++ b/config.py @@ -10,6 +10,7 @@ dotenv_path = os.path.join(basedir, '.env') load_dotenv(dotenv_path) + class Config: NYC_GOV_BASE = 'www1.nyc.gov' WTF_CSRF_ENABLED = True @@ -58,7 +59,7 @@ class Config: SFTP_UPLOAD_DIRECTORY = os.environ.get('SFTP_UPLOAD_DIRECTORY') # Authentication Settings - PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 30))) + PERMANENT_SESSION_LIFETIME = timedelta(minutes=int(os.environ.get('PERMANENT_SESSION_LIFETIME', 20))) SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" MFA_ENCRYPT_FILE = os.environ.get('MFA_ENCRYPT_FILE') From 2a5add90b77b288f852936224da64135fc63dd1a Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 22 Jan 2021 10:57:26 -0500 Subject: [PATCH 16/44] Changes per PR comments --- .env.example | 2 +- app/celery_config.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index df6223c33..5ba8f5834 100644 --- a/.env.example +++ b/.env.example @@ -60,7 +60,7 @@ REDIS_HOST= REDIS_PORT= # Celery -CELERY_CLEAR_EXPIRED_SESSION_IDS= +CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL= # SFTP USE_SFTP=True diff --git a/app/celery_config.py b/app/celery_config.py index 990f3ebb1..6c6439e2d 100644 --- a/app/celery_config.py +++ b/app/celery_config.py @@ -11,7 +11,7 @@ CELERY_IMPORTS = ['app.jobs'] CELERY_TASK_RESULT_EXPIRES = 30 CELERY_TIMEZONE = 'EST' -CELERY_CLEAR_EXPIRED_SESSION_IDS = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS') +CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL') or '*/1' CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] @@ -30,6 +30,6 @@ # Every X minutes 'clear_expired_session_ids': { 'task': 'app.jobs.clear_expired_session_ids', - 'schedule': crontab(minute=CELERY_CLEAR_EXPIRED_SESSION_IDS) + 'schedule': crontab(minute=CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL) } } From a8ce394c87f3774f36f0d67f9d6ebe54539146df Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 22 Jan 2021 11:08:52 -0500 Subject: [PATCH 17/44] Properly set default value for CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL --- app/celery_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/celery_config.py b/app/celery_config.py index 6c6439e2d..34aaaef4d 100644 --- a/app/celery_config.py +++ b/app/celery_config.py @@ -11,7 +11,7 @@ CELERY_IMPORTS = ['app.jobs'] CELERY_TASK_RESULT_EXPIRES = 30 CELERY_TIMEZONE = 'EST' -CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL') or '*/1' +CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL', '*/1') CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] From 476a236a573e6f3a2f96c6537a30ee27b2bd3a2b Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 7 Oct 2021 11:04:59 -0400 Subject: [PATCH 18/44] Updated specific agency instructions for DOT --- data/agencies.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/agencies.json b/data/agencies.json index d2ce1baa8..b37b01304 100644 --- a/data/agencies.json +++ b/data/agencies.json @@ -1554,8 +1554,7 @@ }, "monitor_agency_requests": [], "specific_request_instructions": { - "text": "

Article 6 of the New York State Public Officers Law (FOIL) allows the public to submit written requests for access to Department of Transportation (DOT) records.

If you are seeking documents concerning subway or bus service, please see our website at http://www.nyc.gov/html/dot/html/ferrybus/ferrybus.shtml, which states that \"DOT is not responsible for New York City subway or bus service. The MTA operates the subway system, issues MetroCards, and operates the Metro-North and Long Island Railroads.\"

Before making your request for DOT records, check if the information you are looking for is already available to the public.

Some tips for a successful FOIL records search:

  • Be specific as to the records requested. Indicate the date or date range of records requested. If a request is location based, describe the location either as an address, an intersection or a block
  • The request must only be for records and not a question requiring an answer
  • Request only the records that you want
Cost

DOT charges 25\u00a2 per page of paper documents returned. This fee covers photocopying and must be paid before documents will be sent out.

" - } + "text": "

Article 6 of the New York State Public Officers Law (FOIL) allows the public to submit written requests for access to Department of Transportation (DOT) records.

If you are seeking documents concerning subway or bus service, please see our website at http://www.nyc.gov/html/dot/html/ferrybus/ferrybus.shtml, which states that \"DOT is not responsible for New York City subway or bus service. The MTA operates the subway system, issues MetroCards, and operates the Metro-North and Long Island Railroads.\"

Before making your request for DOT records, check if the information you are looking for is already available to the public.

Some tips for a successful FOIL records search:

  • Be specific as to the records requested. Indicate the date or date range of records requested. If a request is location based, describe the location either as an address, an intersection or a block
  • The request must only be for records and not a question requiring an answer
  • Request only the records that you want
Cost

DOT charges 25¢ per page of paper documents returned. This fee covers photocopying and must be paid before documents will be sent out.

" } }, "acronym": "DOT" }, From 26816f8ee4bb7715e98da1c45e5061934355b551 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Mon, 2 May 2022 15:29:57 -0400 Subject: [PATCH 19/44] Update python pip packages to latest versions --- Pipfile | 3 +- Pipfile.lock | 1814 +++++++++++++++++++++++-------------- app/__init__.py | 12 +- app/admin/forms.py | 8 +- app/agency/api/views.py | 1 + app/auth/forms.py | 6 +- app/models.py | 4 - app/report/forms.py | 10 +- app/request/forms.py | 22 +- app/request/utils.py | 1 + app/request/views.py | 3 +- app/search/utils.py | 95 +- app/user/utils.py | 1 - app/user_request/forms.py | 8 +- 14 files changed, 1239 insertions(+), 749 deletions(-) diff --git a/Pipfile b/Pipfile index 13e71ef46..3010417c9 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +setuptools = "57.5.0" pipdeptree = "*" decorator = "*" Faker = "*" @@ -117,4 +118,4 @@ python-magic = "*" gunicorn = "*" [requires] -python_version = "3.8.3" +python_version = "3.10.2" diff --git a/Pipfile.lock b/Pipfile.lock index 84cd60dc9..f48cdbfba 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "377256ab30a0c7f87f8400719be981b23a8130c9756a87c637255ef193990a6e" + "sha256": "e25cd0829299ca5614d201515ced601ac7a8202f57c1ca4d1d166099d1112f4c" }, "pipfile-spec": 6, "requires": { - "python_version": "3.8.3" + "python_version": "3.10.2" }, "sources": [ { @@ -18,26 +18,26 @@ "default": { "alembic": { "hashes": [ - "sha256:4e02ed2aa796bd179965041afa092c55b51fb077de19d61835673cc80672c01c", - "sha256:5334f32314fb2a56d86b4c4dd1ae34b08c03cae4cb888bc699942104d66bc245" + "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b", + "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58" ], "index": "pypi", - "version": "==1.4.3" + "version": "==1.7.7" }, "amqp": { "hashes": [ - "sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a", - "sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784" + "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2", + "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.1.1" }, "anyjson": { "hashes": [ - "sha256:37812d863c9ad3e35c0734c42e0bf0320ce8c3bed82cd20ad54cb34d158157ba" + "sha256:c7976a859d537cb22cfe130f4987e6a64e7c34edced8123153414d47cb11555d" ], "index": "pypi", - "version": "==0.3.3" + "version": "==0.2.5" }, "appdirs": { "hashes": [ @@ -49,26 +49,37 @@ }, "asn1crypto": { "hashes": [ - "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8", - "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c" + "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", + "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.5.1" + }, + "async-timeout": { + "hashes": [ + "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", + "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" }, "bcrypt": { "hashes": [ + "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d", "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7", "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd", "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" @@ -78,11 +89,11 @@ }, "billiard": { "hashes": [ - "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", - "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" + "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547", + "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b" ], "index": "pypi", - "version": "==3.6.3.0" + "version": "==3.6.4.0" }, "blinker": { "hashes": [ @@ -91,6 +102,73 @@ "index": "pypi", "version": "==1.4" }, + "brotli": { + "hashes": [ + "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d", + "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8", + "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b", + "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c", + "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c", + "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70", + "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f", + "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181", + "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130", + "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19", + "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa", + "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429", + "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126", + "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4", + "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0", + "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b", + "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6", + "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438", + "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f", + "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389", + "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6", + "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26", + "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7", + "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14", + "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2", + "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430", + "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296", + "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12", + "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f", + "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d", + "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a", + "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452", + "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c", + "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761", + "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649", + "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b", + "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea", + "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c", + "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a", + "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031", + "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267", + "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5", + "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7", + "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d", + "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c", + "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43", + "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa", + "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17", + "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb", + "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb", + "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b", + "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4", + "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3", + "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7", + "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1", + "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb", + "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91", + "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b", + "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1", + "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806", + "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3", + "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1" + ], + "version": "==1.0.9" + }, "business-calendar": { "hashes": [ "sha256:2f91a0a41c8b7bd641afa33befc6e8d5e871beb16c827bc2d508f1025e78c635", @@ -109,83 +187,98 @@ }, "cachelib": { "hashes": [ - "sha256:47e95a67d68c729cbad63285a790a06f0e0d27d71624c6e44c1ec3456bb4476f", - "sha256:7df6e05b8dfccdeb869e171575580e5606cf1e82a166633b3cb406bc4f113fd0" + "sha256:0baa926a23924c04ae1354091478b15b3b24e6cf5931dd159452afda5f65babd", + "sha256:6da323fdb16c9f53424a229132646a469b2d046e687fa353b92303910c99bc18" ], - "version": "==0.1.1" + "markers": "python_version >= '3.6'", + "version": "==0.6.0" }, "cairocffi": { "hashes": [ - "sha256:9a979b500c64c8179fec286f337e8fe644eca2f2cd05860ce0b62d25f22ea140" + "sha256:108a3a7cb09e203bdd8501d9baad91d786d204561bd71e9364e8b34897c47b91" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" }, "cairosvg": { "hashes": [ - "sha256:bfa0deea7fa0b9b2f29e41b747a915c249dbca731a4667c2917e47ff96e773e0", - "sha256:f1ff02625520493eafb5695d987f69544555524bb0f95695b9ddd3f9dc7d29d5" + "sha256:98c276b7e4f0caf01e5c7176765c104ffa1aa1461d63b2053b04ab663cf7052b", + "sha256:b0b9929cf5dba005178d746a8036fcf0025550f498ca54db61873322384783bc" ], "index": "pypi", - "version": "==2.5.1" + "version": "==2.5.2" }, "celery": { "hashes": [ - "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13", - "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c" + "sha256:d1398cadf30f576266b34370e28e880306ec55f7a4b6307549b0ae9c15663481", + "sha256:da31f8eae7607b1582e5ee2d3f2d6f58450585afd23379491e3d9229d08102d0" ], "index": "pypi", - "version": "==5.0.5" + "version": "==5.2.6" }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" ], "index": "pypi", - "version": "==2020.12.5" + "version": "==2021.10.8" }, "cffi": { "hashes": [ - "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", - "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", - "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", - "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", - "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", - "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", - "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", - "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", - "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", - "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", - "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", - "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", - "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", - "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", - "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", - "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", - "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", - "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", - "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", - "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", - "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", - "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", - "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", - "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", - "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", - "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", - "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", - "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", - "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", - "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", - "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", - "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", - "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", - "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", - "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", - "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" - ], - "index": "pypi", - "version": "==1.14.4" + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "index": "pypi", + "version": "==1.15.0" }, "chardet": { "hashes": [ @@ -195,19 +288,29 @@ "index": "pypi", "version": "==4.0.0" }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "markers": "python_version >= '3'", + "version": "==2.0.12" + }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", + "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" ], "index": "pypi", - "version": "==7.1.2" + "version": "==8.1.2" }, "click-didyoumean": { "hashes": [ - "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb" + "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", + "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" ], - "version": "==0.0.3" + "markers": "python_full_version >= '3.6.2' and python_version < '4'", + "version": "==0.3.0" }, "click-plugins": { "hashes": [ @@ -218,123 +321,132 @@ }, "click-repl": { "hashes": [ - "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5", - "sha256:b9f29d52abc4d6059f8e276132a111ab8d94980afe6a5432b9d996544afa95d5" + "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b", + "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8" ], - "version": "==0.1.6" + "version": "==0.2.0" }, "convertdate": { "hashes": [ - "sha256:9d2b0cd8d5382d2458d4cfa59665abba398a9e9bfd3a01c6f61b7b47768d28bf", - "sha256:fc34133ef6ceb31738cf1169b528ba487d0164d69f4451a7cef206887c45b71d" + "sha256:770c6b2195544d3e451e230b3f1c9b121ed02680b877f896306a04cf6f26b48f", + "sha256:fcffe3a67522172648cf03b0c3757cfd079726fe5ae04ce29989ad3958039e4e" ], - "version": "==2.2.0" + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.4.0" }, "coverage": { "hashes": [ - "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297", - "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1", - "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497", - "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606", - "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528", - "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b", - "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4", - "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830", - "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1", - "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f", - "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d", - "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3", - "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8", - "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500", - "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7", - "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb", - "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b", - "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059", - "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b", - "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72", - "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36", - "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277", - "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c", - "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631", - "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff", - "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8", - "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec", - "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b", - "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7", - "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105", - "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b", - "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c", - "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b", - "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98", - "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4", - "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879", - "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f", - "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4", - "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044", - "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e", - "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899", - "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f", - "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448", - "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714", - "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2", - "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d", - "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd", - "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7", - "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae" - ], - "index": "pypi", - "version": "==5.3.1" + "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", + "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d", + "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf", + "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7", + "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6", + "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4", + "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059", + "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39", + "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536", + "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac", + "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c", + "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903", + "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d", + "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05", + "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684", + "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1", + "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f", + "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7", + "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca", + "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad", + "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca", + "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d", + "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92", + "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4", + "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf", + "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6", + "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1", + "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4", + "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359", + "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3", + "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620", + "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512", + "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69", + "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2", + "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518", + "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0", + "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa", + "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4", + "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e", + "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1", + "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2" + ], + "index": "pypi", + "version": "==6.3.2" }, "cryptography": { "hashes": [ - "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d", - "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7", - "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901", - "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c", - "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244", - "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6", - "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5", - "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e", - "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c", - "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0", - "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812", - "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a", - "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030", - "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302" - ], - "index": "pypi", - "version": "==3.3.1" + "sha256:0234bdb18620ed16bf186f0591aea0bbc321ecaf59c859d5f5cbe7b646d8377e", + "sha256:183d6a540659c6a729c08971f09f3fb1044c89dd5af9d6f18da824a071f5445d", + "sha256:1af4f31870ef2180aba1c04f6d957461a570c8cabcc4b5ac7fabf2b4a0364ea0", + "sha256:2d3d8a69d262ba27923466194bef637150aef286b11b160e087992206ac32f0c", + "sha256:2dfd682771c04c7e85a4b4ea6aa1682a3fd6f4d9845468fa6ba512b80a560a8d", + "sha256:4c52cb32ea0b9798234823d37c93cab8004c574b2d224f048cd5829d0639387b", + "sha256:57273f69b334c6d30f4d27abc7fb9c919ef4c6193af64420572808302bb45768", + "sha256:5a761fc1ff0eae360a80656bea462c3163dfaa8093b2fa0f72af929217b14a97", + "sha256:5c2517a2c58213ee62b36ee9ece4a710179ddb07db90e31d7619e7ea472c9dc3", + "sha256:710b9041fb97cc576e288b5f96583578ed352dd60608a402045405c388522b94", + "sha256:8921428ca6403d7eb52ee0e728e8b02601060a5791f6d64c8a3a12b5722064af", + "sha256:bc54780dd8f7236874ac29fc155c5cf811f7d910e5f0575932a38bdaac3b5146", + "sha256:bf893131cd79dc8eaf4940b3aa2f4a68eba050471f5deacfaedea6aab04f574f", + "sha256:cab59c774125596fa72f1decc5805894313b40f370a7c75597e37f0211027944", + "sha256:d119feb387ce2df9bfb92e5785df9094325cfa974e2e6aa08c8e4a8b56786afe", + "sha256:d426093df7f00de859bad45d6a09fdab9b8e4c6e46ea897dd0a302b94c7f6871", + "sha256:d886b2c9f8d1ab0916673bc3c89dd04fc6e6591861872c9f08402b0ab2843b82", + "sha256:d97479d943d549d4a78f044b0620a7d349191ed40933ffabff1cc5875e20682c", + "sha256:db1b9516e3072e0342287e06779bec84118bd780f794c8c07bd5da142a526103", + "sha256:e86734f28656f6fd5993ab32bd2d2680c3b8341d6f875faf5212bc78715db2a4", + "sha256:ebdc9c4b3577bb76b0defebe4ef8b866da5228a1c53fbbf394b7677fe292fee9", + "sha256:eee79c6c16949ed817c8cf288e6e124c4b8996e3312d9e7884c71cf9bdda212e" + ], + "index": "pypi", + "version": "==37.0.0" }, "cssselect2": { "hashes": [ - "sha256:2f4a9f20965367bae459e3bb42561f7927e0cfe5b7ea1692757cf67ef5d7dace", - "sha256:93fbb9af860e95dd40bf18c3b2b6ed99189a07c0f29ba76f9c5be71344664ec8" + "sha256:3a83b2a68370c69c9cd3fcb88bbfaebe9d22edeef2c22d1ff3e1ed9c7fa45ed8", + "sha256:5b5d6dea81a5eb0c9ca39f116c8578dd413778060c94c1f51196371618909325" ], "index": "pypi", - "version": "==0.4.1" + "version": "==0.6.0" }, "defusedxml": { "hashes": [ - "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", - "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.7.1" + }, + "deprecated": { + "hashes": [ + "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", + "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.2.13" }, "distlib": { "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" ], - "version": "==0.3.1" + "version": "==0.3.4" }, "dnspython": { "hashes": [ - "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", - "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" + "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", + "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==2.2.1" }, "dominate": { "hashes": [ @@ -344,35 +456,44 @@ "index": "pypi", "version": "==2.6.0" }, + "elastic-transport": { + "hashes": [ + "sha256:10914d0c5c268d9dcfee02cfbef861382d098309ba4eedab820062841bd214b3", + "sha256:869f7d668fb7738776639053fc87499caacbd1bdc7819f0de8025ac0e6cb29ce" + ], + "markers": "python_version >= '3.6'", + "version": "==8.1.2" + }, "elasticsearch": { "hashes": [ - "sha256:4ebd34fd223b31c99d9f3b6b6236d3ac18b3046191a37231e8235b06ae7db955", - "sha256:a725dd923d349ca0652cf95d6ce23d952e2153740cf4ab6daf4a2d804feeed48" + "sha256:70d4ab4a4dcfc2631aaaf58853984b3e7658b85a490f4d2d2c657e91437bb620", + "sha256:d092f4a0d3f4228753cd06d4f70438aec101e13206b31425910d807ec23f232b" ], "index": "pypi", - "version": "==7.10.1" + "version": "==8.1.3" }, "email-validator": { "hashes": [ - "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f", - "sha256:1a13bd6050d1db4475f13e444e169b6fe872434922d38968c67cea9568cce2f0" + "sha256:2323219d19b82f887b64f2a84c6d73f451431bdf87744022c54b1b5bd0bde1bd", + "sha256:565fd3a7aa4516772f55732d50d34d0a18680b5f62995aea8b4a55b62c90c517" ], - "version": "==1.1.2" + "version": "==1.2.0" }, "filelock": { "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85", + "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0" ], - "version": "==3.0.12" + "markers": "python_version >= '3.7'", + "version": "==3.6.0" }, "flask": { "hashes": [ - "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", - "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264", + "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8" ], "index": "pypi", - "version": "==1.1.2" + "version": "==2.1.1" }, "flask-bootstrap": { "hashes": [ @@ -390,11 +511,11 @@ }, "flask-login": { "hashes": [ - "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b", - "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0" + "sha256:5cb01ce4dc253967b6ac722a11e46de83b6272ef7a19cc7b5725ae636916d04d", + "sha256:aa84fcfb4c3cf09ca58c08e816b7bce73f1349ba1cf13d00d8dffc5872d5fcf6" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.6.0" }, "flask-mail": { "hashes": [ @@ -405,19 +526,19 @@ }, "flask-migrate": { "hashes": [ - "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", - "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" + "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9", + "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897" ], "index": "pypi", - "version": "==2.5.3" + "version": "==3.1.0" }, "flask-moment": { "hashes": [ - "sha256:75e1ae59b7562731acf9faf295c0bfd8165f51f67a62bd779e0c57e5f1c66dbf", - "sha256:ff4cc0c4f8ec6798e19ba17fac409a8090f21677da6b21e3e1e4450344d8ed71" + "sha256:1faf8fea08080d2396f0c878b92bd0429635977a8f96c553a0d43f15fd8d0213", + "sha256:cce7c4f39fcf0732bb62a07fbf6cc6305ab5700272fd1c5743c0c0968450a469" ], "index": "pypi", - "version": "==0.11.0" + "version": "==1.0.2" }, "flask-recaptcha": { "hashes": [ @@ -435,19 +556,19 @@ }, "flask-session": { "hashes": [ - "sha256:0768e2bbf06f963ec1aa711bde7aa32dc39ff70f89b495d6db687d899eae4423", - "sha256:d75eb27f918421ccaf1ba86353348b84ecf07fc64ce40ff7ad05a190c4bf50f1" + "sha256:1e3f8a317005db72c831f85d884a5a9d23145f256c730d80b325a3150a22c3db", + "sha256:c9ed54321fa8c4ca0132ffd3369582759eda7252fb4b3bee480e690d1ba41f46" ], "index": "pypi", - "version": "==0.3.2" + "version": "==0.4.0" }, "flask-sqlalchemy": { "hashes": [ - "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e", - "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5" + "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", + "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" ], "index": "pypi", - "version": "==2.4.4" + "version": "==2.5.1" }, "flask-tracy": { "hashes": [ @@ -467,28 +588,107 @@ }, "flask-wtf": { "hashes": [ - "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", - "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" + "sha256:34fe5c6fee0f69b50e30f81a3b7ea16aa1492a771fe9ad0974d164610c09a6c9", + "sha256:9d733658c80be551ce7d5bc13c7a7ac0d80df509be1e23827c847d9520f4359a" ], "index": "pypi", - "version": "==0.14.3" + "version": "==1.0.1" + }, + "fonttools": { + "extras": [ + "woff" + ], + "hashes": [ + "sha256:c0fdcfa8ceebd7c1b2021240bd46ef77aa8e7408cf10434be55df52384865f8e", + "sha256:f829c579a8678fa939a1d9e9894d01941db869de44390adb49ce67055a06cc2a" + ], + "markers": "python_version >= '3.7'", + "version": "==4.33.3" + }, + "greenlet": { + "hashes": [ + "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", + "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", + "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", + "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", + "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", + "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", + "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", + "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", + "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", + "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", + "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", + "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", + "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", + "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", + "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", + "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", + "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", + "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", + "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", + "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", + "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", + "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", + "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", + "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", + "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", + "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", + "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", + "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", + "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", + "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", + "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", + "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", + "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", + "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", + "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", + "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", + "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", + "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", + "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", + "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", + "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", + "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", + "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", + "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", + "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", + "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", + "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", + "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", + "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", + "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", + "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", + "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", + "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", + "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", + "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" + ], + "markers": "python_version >= '3' and (platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))))", + "version": "==1.1.2" }, "gunicorn": { "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" ], "index": "pypi", - "version": "==20.0.4" + "version": "==20.1.0" + }, + "hijri-converter": { + "hashes": [ + "sha256:50648d45a0b850c6d4a3aa7cf88ec4c03ee44740c339ee2bb021c7bb69791dcc", + "sha256:e7149cececca647bf40689ec89bf593c7252b31d699ea587ad0bce91ba42d382" + ], + "markers": "python_version >= '3.6'", + "version": "==2.2.3" }, "holidays": { "hashes": [ - "sha256:72378e7c00139ab157adf5155c0ec7af62489b7cae8d66dfc53a9a02ddcb1cab", - "sha256:72c8686b649364013e16ffc216dc616e7e81f6c1fa7ecf2afe342826178b482d", - "sha256:7fb3fe2fd4823f1169b0695bf2b79135422cce10f5aa9c722213aa3f082a16a2" + "sha256:c6f7c3ab8ada94806702da931d94d37cd61bcfa92cb4d39d351b6a9c5210675c", + "sha256:ca944d20762f027770ceae2f21d037f959c9b206afe92db97161ce78538c275e" ], "index": "pypi", - "version": "==0.10.4" + "version": "==0.13" }, "html5lib": { "hashes": [ @@ -500,51 +700,51 @@ }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], "index": "pypi", - "version": "==2.10" + "version": "==3.3" }, "isodate": { "hashes": [ - "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", - "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81" + "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", + "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.6.1" }, "itsdangerous": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" ], "index": "pypi", - "version": "==1.1.0" + "version": "==2.1.2" }, "jinja2": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", + "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" ], "index": "pypi", - "version": "==2.11.2" + "version": "==3.1.1" }, "jsonschema": { "hashes": [ - "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", - "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" + "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83", + "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823" ], "index": "pypi", - "version": "==3.2.0" + "version": "==4.4.0" }, "kombu": { "hashes": [ - "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006", - "sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c" + "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610", + "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.2.4" }, "korean-lunar-calendar": { "hashes": [ @@ -555,120 +755,159 @@ }, "ldap3": { "hashes": [ - "sha256:10bdd23b612e942ce90ea4dbc744dfd88735949833e46c5467a2dcf68e60f469", - "sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7", - "sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0", - "sha256:8f59a7b5399555b22db06f153daa76c77ded2dd84bc0f0ffe5b0b33901b6eac4", - "sha256:bed71c6ce2f70a00a330eed0c8370664c065239d45bcbe1b82517b6f6eed7f25" + "sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6", + "sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687", + "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", + "sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5", + "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.9.1" }, "lxml": { "hashes": [ - "sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d", - "sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37", - "sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01", - "sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2", - "sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644", - "sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75", - "sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80", - "sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2", - "sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780", - "sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98", - "sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308", - "sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf", - "sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388", - "sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d", - "sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3", - "sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8", - "sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af", - "sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2", - "sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e", - "sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939", - "sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03", - "sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d", - "sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a", - "sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5", - "sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a", - "sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711", - "sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf", - "sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089", - "sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505", - "sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b", - "sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f", - "sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc", - "sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e", - "sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931", - "sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc", - "sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe", - "sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e" - ], - "index": "pypi", - "version": "==4.6.2" + "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169", + "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428", + "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc", + "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85", + "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696", + "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507", + "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3", + "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430", + "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03", + "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9", + "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b", + "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7", + "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5", + "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654", + "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca", + "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9", + "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c", + "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63", + "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe", + "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9", + "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9", + "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1", + "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939", + "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68", + "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613", + "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63", + "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e", + "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4", + "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79", + "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1", + "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e", + "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141", + "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb", + "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939", + "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a", + "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93", + "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9", + "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2", + "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6", + "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa", + "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150", + "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea", + "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33", + "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76", + "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807", + "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a", + "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4", + "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15", + "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f", + "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429", + "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c", + "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5", + "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870", + "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b", + "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8", + "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c", + "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87", + "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0", + "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23", + "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170", + "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f" + ], + "index": "pypi", + "version": "==4.8.0" }, "mako": { "hashes": [ - "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27", - "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9" + "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba", + "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39" ], "index": "pypi", - "version": "==1.1.3" + "version": "==1.2.0" }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "index": "pypi", - "version": "==1.1.1" + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "index": "pypi", + "version": "==2.1.1" }, "oauthlib": { "hashes": [ - "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", - "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" + "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2", + "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe" ], "index": "pypi", - "version": "==3.1.0" + "version": "==3.2.0" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" }, "paramiko": { "hashes": [ - "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", - "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" + "sha256:3c9ed6084f4b671ab66dc3c729092d32d96c3258f1426071301cb33654b09027", + "sha256:3d2e650b6812ce6d160abff701d6ef4434ec97934b13e95cf1ad3da70ffb5c58" ], "index": "pypi", - "version": "==2.7.2" + "version": "==2.10.4" }, "pdfrw": { "hashes": [ @@ -680,94 +919,133 @@ }, "pillow": { "hashes": [ - "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6", - "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865", - "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174", - "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032", - "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a", - "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e", - "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378", - "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17", - "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c", - "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913", - "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7", - "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0", - "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820", - "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba", - "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2", - "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b", - "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9", - "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234", - "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d", - "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5", - "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206", - "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9", - "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8", - "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59", - "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d", - "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a", - "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b", - "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d" - ], - "index": "pypi", - "version": "==8.1.0" + "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e", + "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595", + "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512", + "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c", + "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477", + "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a", + "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4", + "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e", + "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5", + "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378", + "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a", + "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652", + "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7", + "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a", + "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a", + "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6", + "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165", + "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160", + "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331", + "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b", + "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458", + "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033", + "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8", + "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481", + "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58", + "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7", + "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3", + "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea", + "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34", + "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3", + "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8", + "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581", + "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244", + "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef", + "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0", + "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2", + "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97", + "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717" + ], + "index": "pypi", + "version": "==9.1.0" }, "pkgconfig": { "hashes": [ - "sha256:97bfe3d981bab675d5ea3ef259045d7919c93897db7d3b59d4e8593cba8d354f", - "sha256:cddf2d7ecadb272178a942eb852a9dee46bda2adcc36c3416b0fef47a4ed9f38" + "sha256:d20023bbeb42ee6d428a0fac6e0904631f545985a10cdd71a20aa58bc47a4209", + "sha256:deb4163ef11f75b520d822d9505c1f462761b4309b1bb713d08689759ea8b899" ], "index": "pypi", - "version": "==1.5.1" + "version": "==1.5.5" + }, + "platformdirs": { + "hashes": [ + "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", + "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" + ], + "markers": "python_version >= '3.7'", + "version": "==2.5.2" }, "prompt-toolkit": { "hashes": [ - "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7", - "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5" + "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752", + "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7" ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.10" + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.29" }, "psycopg2-binary": { "hashes": [ - "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", - "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", - "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", - "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", - "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", - "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", - "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", - "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", - "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", - "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", - "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", - "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", - "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", - "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", - "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", - "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", - "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", - "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", - "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", - "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", - "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", - "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", - "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", - "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", - "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", - "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", - "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", - "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", - "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", - "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", - "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", - "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", - "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", - "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", - "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" - ], - "index": "pypi", - "version": "==2.8.6" + "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7", + "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76", + "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa", + "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9", + "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004", + "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1", + "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094", + "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57", + "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af", + "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554", + "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232", + "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c", + "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b", + "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834", + "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2", + "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71", + "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460", + "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e", + "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4", + "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d", + "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d", + "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9", + "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f", + "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063", + "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478", + "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092", + "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c", + "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce", + "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1", + "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65", + "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e", + "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4", + "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029", + "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33", + "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39", + "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53", + "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307", + "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42", + "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35", + "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8", + "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb", + "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae", + "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e", + "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f", + "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba", + "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24", + "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca", + "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb", + "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef", + "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42", + "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1", + "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667", + "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272", + "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281", + "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e", + "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd" + ], + "index": "pypi", + "version": "==2.9.3" }, "pyasn1": { "hashes": [ @@ -790,96 +1068,116 @@ }, "pycparser": { "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], "index": "pypi", - "version": "==2.20" + "version": "==2.21" + }, + "pydyf": { + "hashes": [ + "sha256:1e2f5de48174f505de025a7d1e7cf01bbdd9422ca8ab9451782bf00ee178602c", + "sha256:9ef1505c424e2ac9c4caef5b8f8c105a311c23fdb4db4979d7d82ad6cff76fa7" + ], + "markers": "python_version >= '3.6'", + "version": "==0.1.2" }, "pymeeus": { "hashes": [ - "sha256:56430209e6f9a039f1ba73f107ab0b23121548e2a67ed2855f2416c3749a5662" + "sha256:bb9d670818d8b0594317b48a7dadea02a0594e5344263bf2054e1a011c8fed55" ], - "version": "==0.3.7" + "version": "==0.5.11" }, "pynacl": { "hashes": [ - "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", - "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", - "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", - "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", - "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", - "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", - "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", - "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", - "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", - "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", - "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", - "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", - "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", - "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", - "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", - "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", - "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", - "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", + "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", + "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", + "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", + "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", + "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", + "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", + "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", + "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", + "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.5.0" }, "pyopenssl": { "hashes": [ - "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", - "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" + "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", + "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" ], "index": "pypi", - "version": "==20.0.1" + "version": "==22.0.0" }, "pyotp": { "hashes": [ - "sha256:038a3f70b34eaad3f72459e8b411662ef8dfcdd95f7d9203fa489e987a75584b", - "sha256:ef07c393660529261e66902e788b32e46260d2c29eb740978df778260a1c2b4c" + "sha256:9d144de0f8a601d6869abe1409f4a3f75f097c37b50a36a3bf165810a6e23f28", + "sha256:d28ddfd40e0c1b6a6b9da961c7d47a10261fb58f378cb00f05ce88b26df9c432" ], "index": "pypi", - "version": "==2.4.1" + "version": "==2.6.0" }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", + "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" ], "index": "pypi", - "version": "==2.4.7" + "version": "==3.0.8" }, "pyphen": { "hashes": [ - "sha256:624b036eafe6f38fad3e79ee676ec711a8ce634feb9f34ac615bbaf73e662111", - "sha256:719b21dfb4b04fbc11cc0f6112418535fe35474021120cccfffc43a25fe63128" + "sha256:459020cd320eb200c0c5ba46b98b2278fd34c5546f520fdcd2ce5f8d733eb994", + "sha256:b7d3dfc24b6f2178cdb2b1757ace0bd5d222de3e62c28d22ac578c5f22a13e9b" ], "index": "pypi", - "version": "==0.10.0" + "version": "==0.12.0" }, "pyrsistent": { "hashes": [ - "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" - ], - "markers": "python_version >= '3.5'", - "version": "==0.17.3" + "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", + "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", + "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", + "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", + "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", + "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", + "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", + "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", + "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", + "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", + "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", + "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", + "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", + "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", + "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", + "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", + "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", + "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", + "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", + "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", + "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.1" }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.2" }, "python-dotenv": { "hashes": [ - "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", - "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" + "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f", + "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938" ], "index": "pypi", - "version": "==0.15.0" + "version": "==0.20.0" }, "python-editor": { "hashes": [ @@ -894,23 +1192,37 @@ }, "python-magic": { "hashes": [ - "sha256:356efa93c8899047d1eb7d3eb91e871ba2f5b1376edbaf4cc305e3c872207355", - "sha256:b757db2a5289ea3f1ced9e60f072965243ea43a2221430048fd8cacab17be0ce" + "sha256:1a2c81e8f395c744536369790bd75094665e9644110a6623bcc3bbea30f03973", + "sha256:21f5f542aa0330f5c8a64442528542f6215c8e18d2466b399b0d9d39356d83fc" ], "index": "pypi", - "version": "==0.4.18" + "version": "==0.4.25" }, "python-magic-bin": { - "sys_platform": "=='darwin'", - "version": "*" + "hashes": [ + "sha256:34a788c03adde7608028203e2dbb208f1f62225ad91518787ae26d603ae68892", + "sha256:7b1743b3dbf16601d6eedf4e7c2c9a637901b0faaf24ad4df4d4527e7d8f66a4", + "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69" + ], + "index": "pypi", + "markers": "sys_platform == 'darwin'", + "version": "==0.4.14" }, "pytz": { "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", + "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" ], "index": "pypi", - "version": "==2019.3" + "version": "==2022.1" + }, + "pytz-deprecation-shim": { + "hashes": [ + "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6", + "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.1.0.post0" }, "raven": { "hashes": [ @@ -922,28 +1234,35 @@ }, "redis": { "hashes": [ - "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", - "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" + "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f", + "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2" ], "index": "pypi", - "version": "==3.5.3" + "version": "==4.2.2" }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.25.1" + "version": "==2.27.1" }, "requests-oauthlib": { "hashes": [ - "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", - "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", - "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" + "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", + "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.3.1" + }, + "setuptools": { + "hashes": [ + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + ], + "markers": "python_version >= '3.7'", + "version": "==62.1.0" }, "simplekv": { "hashes": [ @@ -956,87 +1275,93 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "index": "pypi", - "version": "==1.15.0" + "version": "==1.16.0" }, "sqlalchemy": { "hashes": [ - "sha256:04f995fcbf54e46cddeb4f75ce9dfc17075d6ae04ac23b2bacb44b3bc6f6bf11", - "sha256:0c6406a78a714a540d980a680b86654feadb81c8d0eecb59f3d6c554a4c69f19", - "sha256:0c72b90988be749e04eff0342dcc98c18a14461eb4b2ad59d611b57b31120f90", - "sha256:108580808803c7732f34798eb4a329d45b04c562ed83ee90f09f6a184a42b766", - "sha256:1418f5e71d6081aa1095a1d6b567a562d2761996710bdce9b6e6ba20a03d0864", - "sha256:17610d573e698bf395afbbff946544fbce7c5f4ee77b5bcb1f821b36345fae7a", - "sha256:216ba5b4299c95ed179b58f298bda885a476b16288ab7243e89f29f6aeced7e0", - "sha256:2ff132a379838b1abf83c065be54cef32b47c987aedd06b82fc76476c85225eb", - "sha256:314f5042c0b047438e19401d5f29757a511cfc2f0c40d28047ca0e4c95eabb5b", - "sha256:318b5b727e00662e5fc4b4cd2bf58a5116d7c1b4dd56ffaa7d68f43458a8d1ed", - "sha256:3ab5b44a07b8c562c6dcb7433c6a6c6e03266d19d64f87b3333eda34e3b9936b", - "sha256:426ece890153ccc52cc5151a1a0ed540a5a7825414139bb4c95a868d8da54a52", - "sha256:491fe48adc07d13e020a8b07ef82eefc227003a046809c121bea81d3dbf1832d", - "sha256:4a84c7c7658dd22a33dab2e2aa2d17c18cb004a42388246f2e87cb4085ef2811", - "sha256:54da615e5b92c339e339fe8536cce99fe823b6ed505d4ea344852aefa1c205fb", - "sha256:5a7f224cdb7233182cec2a45d4c633951268d6a9bcedac37abbf79dd07012aea", - "sha256:61628715931f4962e0cdb2a7c87ff39eea320d2aa96bd471a3c293d146f90394", - "sha256:62285607a5264d1f91590abd874d6a498e229d5840669bd7d9f654cfaa599bd0", - "sha256:62fb881ba51dbacba9af9b779211cf9acff3442d4f2993142015b22b3cd1f92a", - "sha256:68428818cf80c60dc04aa0f38da20ad39b28aba4d4d199f949e7d6e04444ea86", - "sha256:6aaa13ee40c4552d5f3a59f543f0db6e31712cc4009ec7385407be4627259d41", - "sha256:70121f0ae48b25ef3e56e477b88cd0b0af0e1f3a53b5554071aa6a93ef378a03", - "sha256:715b34578cc740b743361f7c3e5f584b04b0f1344f45afc4e87fbac4802eb0a0", - "sha256:758fc8c4d6c0336e617f9f6919f9daea3ab6bb9b07005eda9a1a682e24a6cacc", - "sha256:7d4b8de6bb0bc736161cb0bbd95366b11b3eb24dd6b814a143d8375e75af9990", - "sha256:81d8d099a49f83111cce55ec03cc87eef45eec0d90f9842b4fc674f860b857b0", - "sha256:888d5b4b5aeed0d3449de93ea80173653e939e916cc95fe8527079e50235c1d2", - "sha256:95bde07d19c146d608bccb9b16e144ec8f139bcfe7fd72331858698a71c9b4f5", - "sha256:9bf572e4f5aa23f88dd902f10bb103cb5979022a38eec684bfa6d61851173fec", - "sha256:bab5a1e15b9466a25c96cda19139f3beb3e669794373b9ce28c4cf158c6e841d", - "sha256:bd4b1af45fd322dcd1fb2a9195b4f93f570d1a5902a842e3e6051385fac88f9c", - "sha256:bde677047305fe76c7ee3e4492b545e0018918e44141cc154fe39e124e433991", - "sha256:c389d7cc2b821853fb018c85457da3e7941db64f4387720a329bc7ff06a27963", - "sha256:d055ff750fcab69ca4e57b656d9c6ad33682e9b8d564f2fbe667ab95c63591b0", - "sha256:d53f59744b01f1440a1b0973ed2c3a7de204135c593299ee997828aad5191693", - "sha256:f115150cc4361dd46153302a640c7fa1804ac207f9cc356228248e351a8b4676", - "sha256:f1e88b30da8163215eab643962ae9d9252e47b4ea53404f2c4f10f24e70ddc62", - "sha256:f8191fef303025879e6c3548ecd8a95aafc0728c764ab72ec51a0bdf0c91a341" - ], - "index": "pypi", - "version": "==1.3.22" + "sha256:09c606d8238feae2f360b8742ffbe67741937eb0a05b57f536948d198a3def96", + "sha256:166a3887ec355f7d2f12738f7fa25dc8ac541867147a255f790f2f41f614cb44", + "sha256:16abf35af37a3d5af92725fc9ec507dd9e9183d261c2069b6606d60981ed1c6e", + "sha256:2e885548da361aa3f8a9433db4cfb335b2107e533bf314359ae3952821d84b3e", + "sha256:2ec89bf98cc6a0f5d1e28e3ad28e9be6f3b4bdbd521a4053c7ae8d5e1289a8a1", + "sha256:2ecac4db8c1aa4a269f5829df7e706639a24b780d2ac46b3e485cbbd27ec0028", + "sha256:316c7e5304dda3e3ad711569ac5d02698bbc71299b168ac56a7076b86259f7ea", + "sha256:5041474dcab7973baa91ec1f3112049a9dd4652898d6a95a6a895ff5c58beb6b", + "sha256:53d2d9ee93970c969bc4e3c78b1277d7129554642f6ffea039c282c7dc4577bc", + "sha256:5864a83bd345871ad9699ce466388f836db7572003d67d9392a71998092210e3", + "sha256:5c90ef955d429966d84326d772eb34333178737ebb669845f1d529eb00c75e72", + "sha256:5d50cb71c1dbed70646d521a0975fb0f92b7c3f84c61fa59e07be23a1aaeecfc", + "sha256:64678ac321d64a45901ef2e24725ec5e783f1f4a588305e196431447e7ace243", + "sha256:64d796e9af522162f7f2bf7a3c5531a0a550764c426782797bbeed809d0646c5", + "sha256:6cb4c4f57a20710cea277edf720d249d514e587f796b75785ad2c25e1c0fed26", + "sha256:6e1fe00ee85c768807f2a139b83469c1e52a9ffd58a6eb51aa7aeb524325ab18", + "sha256:6e859fa96605027bd50d8e966db1c4e1b03e7b3267abbc4b89ae658c99393c58", + "sha256:7a052bd9f53004f8993c624c452dfad8ec600f572dd0ed0445fbe64b22f5570e", + "sha256:81e53bd383c2c33de9d578bfcc243f559bd3801a0e57f2bcc9a943c790662e0c", + "sha256:83cf3077712be9f65c9aaa0b5bc47bc1a44789fd45053e2e3ecd59ff17c63fe9", + "sha256:8b20c4178ead9bc398be479428568ff31b6c296eb22e75776273781a6551973f", + "sha256:8d07fe2de0325d06e7e73281e9a9b5e259fbd7cbfbe398a0433cbb0082ad8fa7", + "sha256:a0ae3aa2e86a4613f2d4c49eb7da23da536e6ce80b2bfd60bbb2f55fc02b0b32", + "sha256:af2587ae11400157753115612d6c6ad255143efba791406ad8a0cbcccf2edcb3", + "sha256:b3db741beaa983d4cbf9087558620e7787106319f7e63a066990a70657dd6b35", + "sha256:be094460930087e50fd08297db9d7aadaed8408ad896baf758e9190c335632da", + "sha256:cb441ca461bf97d00877b607f132772644b623518b39ced54da433215adce691", + "sha256:ce20f5da141f8af26c123ebaa1b7771835ca6c161225ce728962a79054f528c3", + "sha256:d57ac32f8dc731fddeb6f5d1358b4ca5456e72594e664769f0a9163f13df2a31", + "sha256:dce3468bf1fc12374a1a732c9efd146ce034f91bb0482b602a9311cb6166a920", + "sha256:e12532c4d3f614678623da5d852f038ace1f01869b89f003ed6fe8c793f0c6a3", + "sha256:e74ce103b81c375c3853b436297952ef8d7863d801dcffb6728d01544e5191b5", + "sha256:f0394a3acfb8925db178f7728adb38c027ed7e303665b225906bfa8099dc1ce8", + "sha256:f522214f6749bc073262529c056f7dfd660f3b5ec4180c5354d985eb7219801e", + "sha256:fbf8c09fe9728168f8cc1b40c239eab10baf9c422c18be7f53213d70434dea43", + "sha256:fca8322e04b2dde722fcb0558682740eebd3bd239bea7a0d0febbc190e99dc15" + ], + "index": "pypi", + "version": "==1.4.36" }, "tablib": { "hashes": [ - "sha256:41aa40981cddd7ec4d1fabeae7c38d271601b306386bd05b5c3bcae13e5aeb20", - "sha256:f83cac08454f225a34a305daa20e2110d5e6335135d505f93bc66583a5f9c10d" + "sha256:870d7e688f738531a14937a055e8bba404fbc388e77d4d500b2c904075d1019c", + "sha256:a57f2770b8c225febec1cb1e65012a69cf30dd28be810e0ff98d024768c7d0f1" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.2.1" }, "tinycss2": { "hashes": [ - "sha256:0353b5234bcaee7b1ac7ca3dea7e02cd338a9f8dcbb8f2dcd32a5795ec1e5f9a", - "sha256:fbdcac3044d60eb85fdb2aa840ece43cf7dbe798e373e6ee0be545d4d134e18a" + "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf", + "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8" ], "index": "pypi", - "version": "==1.1.0" + "version": "==1.1.1" + }, + "tzdata": { + "hashes": [ + "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", + "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" + ], + "markers": "python_version >= '3.6'", + "version": "==2022.1" }, "tzlocal": { "hashes": [ - "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", - "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4" + "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745", + "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7" ], "index": "pypi", - "version": "==2.1" + "version": "==4.2" }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], "index": "pypi", - "version": "==1.26.2" + "version": "==1.26.9" }, "vine": { "hashes": [ @@ -1048,11 +1373,11 @@ }, "virtualenv": { "hashes": [ - "sha256:205a7577275dd0d9223c730dd498e21a8910600085c3dee97412b041fc4b853b", - "sha256:7992b8de87e544a4ab55afc2240bf8388c4e3b5765d03784dad384bfdf9097ee" + "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a", + "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5" ], "index": "pypi", - "version": "==20.3.0" + "version": "==20.14.1" }, "visitor": { "hashes": [ @@ -1070,11 +1395,11 @@ }, "weasyprint": { "hashes": [ - "sha256:21a1a9f11650ed14241817bf333a0ae0a42f6ae38cd7c2654845cb17352b7434", - "sha256:36b897d219e1944a1a0c97193a121fc175db326c660c50d937e874a995d9c716" + "sha256:12260f1be1052e5c5b6e8bc90069969c6ee7ad77178e02398763ced5ea2cbc11", + "sha256:e04da040630566c44caa202033f07f858765f536709045a809739f3d048e89d6" ], "index": "pypi", - "version": "==52.2" + "version": "==54.3" }, "webencodings": { "hashes": [ @@ -1086,44 +1411,180 @@ }, "werkzeug": { "hashes": [ - "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", - "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" - ], - "index": "pypi", - "version": "==1.0.1" + "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6", + "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74" + ], + "index": "pypi", + "version": "==2.1.1" + }, + "wrapt": { + "hashes": [ + "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b", + "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0", + "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330", + "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3", + "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68", + "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa", + "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe", + "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd", + "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b", + "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80", + "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38", + "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f", + "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350", + "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd", + "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb", + "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3", + "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0", + "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff", + "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c", + "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758", + "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036", + "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb", + "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763", + "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9", + "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7", + "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1", + "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7", + "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0", + "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5", + "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce", + "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8", + "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279", + "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0", + "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06", + "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561", + "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a", + "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311", + "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131", + "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4", + "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291", + "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4", + "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8", + "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8", + "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d", + "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c", + "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd", + "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d", + "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6", + "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775", + "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e", + "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627", + "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e", + "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8", + "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1", + "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48", + "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc", + "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3", + "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6", + "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425", + "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d", + "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23", + "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c", + "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33", + "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.14.0" }, "wtforms": { "extras": [ "email" ], "hashes": [ - "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c", - "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c" + "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc", + "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b" ], "index": "pypi", - "version": "==2.3.3" + "version": "==3.0.1" }, "xmlsec": { "hashes": [ - "sha256:252f79ed4482d6eefcca62c3bfc99b8d95c07abd846262d854a207ec4d67fac5", - "sha256:31884dc97cc34cf1681a0f239f613969e61f9a01f4c2d2a62e53d68216fe42d6", - "sha256:32a669dfe447bccecdb4ef79221c0452ce6dad919f3a75daf512792141a54dac", - "sha256:3d13d7b6cb921dbc4d60d00ad00081a038df73a1e69f5bcc3695deb1bf2093b0", - "sha256:5e2f263a21fd146859911479ec35e40a57f519e650f56c775f91367d2a1b6e15", - "sha256:61076be98da4c7cf842a78aa3f129a5039f2ba4992e02480eefe78028d317698", - "sha256:69d7f965d6b74b3266f7baa99a0377d9c76acbf26c615b4ee8d2cbe17bf85528", - "sha256:6d8bb24c3a4db398011f394e29b58cd34c9c26d76b772c5d418d8579df127234", - "sha256:6d9d46d1f6b4985023469a1e334cb35c7c8fc6bd9d8b65ca52b923a7a6869c2a", - "sha256:8a7ffdc4f7f760253aa4dd8d2037358eb33915ca1dcf1c2422b19fcf0ab68506", - "sha256:927fc5755bb93dc09275bd5d818811e016290c194012d63f8e6f86b7ece3e468", - "sha256:dcaa084c3700f775eba09d81a1432444f82d9ad6270320c56c1a733d71cceb3a", - "sha256:f59698cc0366395ca79b48b080674973541aae290670c57d88f05d939a4c00da" - ], - "index": "pypi", - "version": "==1.3.9" + "sha256:135724cdce60e6bbd072fca6f09a21f72e2cecc59eebb4eed7740c316ecabc7b", + "sha256:1b4377f6d37ad714ba95a227ef40fb54ba1b22ef5170ce04c330fe45ee6ad184", + "sha256:2c86ac6ce570c9e04f04da0cd5e7d3db346e4b5b1d006311606368f17c756ef9", + "sha256:4e5f565de311afa33aaee4724566e685f951afe301212b6cf82f98cf9d8a1749", + "sha256:9a2b8a780093b0fe8cecae53a81a8cd9edd50c08980d374c5317c91f065042d9", + "sha256:ce9c681adbc87b4f06c2b16725d9b2edbdbd508117dae4288b5faf78c1406038", + "sha256:d22da4d3dcc559fb2e54e782f39c9ddad5f8d5b356f86a79bbb80b0a45115c97", + "sha256:db3e18ca883c01bbe28c9f5197c66f676c9772cf2d85f667e6122fc4d0702225", + "sha256:e4783f7814aa2a3e318385cce8ef87c82954b9a59535a48f67da4e2c21c08ce1", + "sha256:f32e54065f0404ceff71388daa7fa7df10e1fb800051dfe302d63abb0acf0020", + "sha256:f5d242b1a19a36078608f5d7f4d561c5ca55cac8061a323a071c06275267dc19" + ], + "index": "pypi", + "version": "==1.3.12" + }, + "zopfli": { + "hashes": [ + "sha256:0190633ca26568f6fa810af9b7c108279d5f565a8aca1224333ff732f565086d", + "sha256:0f052a07a6fb6b7bf87ed30a099187597eb594a8a573a4426c16a6a852a05d86", + "sha256:0f45111b31f32aef070be180433fec5de548dc87caf92c5c304d1e3642b12815", + "sha256:195209711463a399f0c252b34042d8027f3b41dcb646fc112551cfaef507eab4", + "sha256:2442b99067229d786aa9f9b86976db1ba9602c1b204f361d2901d1bbcaca3441", + "sha256:28a103434694ce35cbe4f2380e18077052a78f1a0de061b3c8f1bd35b54c2822", + "sha256:29357c34b8de35c05dd04d8706393bfa09b2dd8bb7a331cc5e98e2b8d39d2efd", + "sha256:2cd55bf9952884f3c0a3a32f95e5ba5fb0e1f371c9b1a99ed41351c1e00f4700", + "sha256:2f61ecd57bc47684c44a60e8cecb8e67f633cf238f30cc255627e172119ad72d", + "sha256:338c7b2bba06ff60a73f724b7e1c8a16a5aebe9155edf58cf69b4ff7cb0b46f6", + "sha256:3663d25419476b9e999e12ab8f6f5f8d39079bf536947da2cb5f3e45491db6f4", + "sha256:37071a3dd5ec0c7f27a1f440d6f50ed8171665e7e3b6ae9a6228e6c48d21570c", + "sha256:38b1928a5fc5c706ec90aa833ffb5f4512bf886fe41c1b30a95edf7fb09544d6", + "sha256:51537999c2114a68b1c0fac8f7ab2a05e4251d778b568213f1666f04feb79e1c", + "sha256:58b2bd497273e1344098370d959f837ec1d18bae9bfafad8f4e4a2802cbbf049", + "sha256:5ee224ec851b4b53042ff6ad6e81712c8c9aa18396396acb024fe5a65c6bb8f1", + "sha256:5fa38b9bb6636fd11b3348dfd6aee4839e71145c3aebc76de4ba44886ec9fd6e", + "sha256:5ffcdc5ee695da73990f7806f8f1dd9a6d99d338bbe54d08fb3aaa55c9603e27", + "sha256:683c2d521553aa8ca4526c911d419d37223db298f76048ce920aded18ae060cb", + "sha256:7b189d80aeedb986d226e966df9e13fe8ccc28352d9d5c6c1bd3ac208aa79769", + "sha256:7c829c577f976b05e4ec583da4f48f31448db97b9f7b65c438d45ba7893aa2a7", + "sha256:7e2b47662632809d7035f4fc16edbe141c4538158ad74eb3c47532b8bedb8277", + "sha256:890a83502bb5dff27b1e2b829f8b879d9b7c1383d20a68dc13f0de71da4ff604", + "sha256:8a40d9d113902aea0de370dce115051cf9cae4767b50af4fdde66d931b9bcae5", + "sha256:8bc92b4008854afa465d647be5ae51b01788ec47cfd10a362dcc865ff898c473", + "sha256:95a7ca4571797375b1fa924858cda344767621ef70a43b1b2c0b116e275f42b2", + "sha256:9e123bfb16cf86ef5abda375012a97c7d00d989b3519d785ca298326f671a81b", + "sha256:a8ca5b541544a7b959fdf5a8f614c52a31002e4be489663d835aadeef3473cb0", + "sha256:a9def54c90edd112f785b07a812005ab374e7d0aaf50ecac900ca0c51adab3e7", + "sha256:aa11904718fd27b2ccadf0ac88c1ca0b96ba67ad0c3c2bd584f85de060d04534", + "sha256:aa18771d1d76c09d2ec2c859b6c55fdad29d2511549b4225437194aca102ad3c", + "sha256:b25540fee3d54e0fb627f911f7e1bed79611ea09aa048708777a34f6f1ac9b70", + "sha256:b882f85b13c47eb19e7f07bccac7564701424ed5ec6d7ba8886ad6de04110e21", + "sha256:ba4001a8c798a9bb2a59bb30284acd604e0c702477dce69b7fde35a50e55a95b", + "sha256:bbcdbfe93dad34f0e30f166092ffdf95e564e415b29732a6f6a52def7bb1c4d3", + "sha256:c5d722b2bfde6dbfbda548e7f6b5b50b57ee06f334b055be24d9bbcdbee60e84", + "sha256:c993919cd01ea5c4b0134908bbd59321d9bdbb9085aa30c538fa1289745fe52f", + "sha256:c9b9f73d99080ca2d79600683b8be3395d46a386b4e1d351c6ed6dc261d267e9", + "sha256:cfffa5ad327585754f811fd49518d7170d200ba7888c49ae8629ae94c6c4a77d", + "sha256:d067f39d72f364ae94a118df3b4cf2db8a9f53625f497ad0f04bf91ea8720db0", + "sha256:d8457452a9151b56f17bbb9af57a4764fb41958ab84bf808e3296aefb6e61bca", + "sha256:dbf30730f169bbe77b2b6e000bf8b486559e8a36e6ab82f2471d75be4661e6bc", + "sha256:e5263d2806e2c1ccb23f52b2972a235d31d42f22f3fa3032cc9aded51e9bf2c6", + "sha256:e9091778e9e0dbbded72c389eace553153102acc9da560870d9d0845c8547a8d", + "sha256:f04a2cf50797dfa6fa954dd24de533f471542aa1923b89381a046a936bcbdcc5", + "sha256:faf26674e52a957b8fd76e955b6d89215265f4b8c7e13abf834c84a8e23def9d", + "sha256:fd917247bb0489c924d74186075cf0a2d7d06b3b1413197f9f6ee8fcbd0263a0", + "sha256:fe9c9276edafb8746c01be15e5d702dcdab41062e2f7fb8af1dd8fa51a18b717" + ], + "version": "==0.2.1" } }, "develop": { + "appnope": { + "hashes": [ + "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", + "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.3" + }, + "asttokens": { + "hashes": [ + "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c", + "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5" + ], + "version": "==2.0.5" + }, "atomicwrites": { "hashes": [ "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", @@ -1134,11 +1595,11 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" }, "backcall": { "hashes": [ @@ -1149,74 +1610,73 @@ }, "coverage": { "hashes": [ - "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297", - "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1", - "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497", - "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606", - "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528", - "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b", - "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4", - "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830", - "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1", - "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f", - "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d", - "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3", - "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8", - "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500", - "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7", - "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb", - "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b", - "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059", - "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b", - "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72", - "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36", - "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277", - "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c", - "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631", - "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff", - "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8", - "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec", - "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b", - "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7", - "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105", - "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b", - "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c", - "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b", - "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98", - "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4", - "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879", - "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f", - "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4", - "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044", - "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e", - "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899", - "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f", - "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448", - "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714", - "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2", - "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d", - "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd", - "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7", - "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae" - ], - "index": "pypi", - "version": "==5.3.1" + "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", + "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d", + "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf", + "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7", + "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6", + "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4", + "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059", + "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39", + "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536", + "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac", + "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c", + "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903", + "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d", + "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05", + "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684", + "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1", + "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f", + "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7", + "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca", + "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad", + "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca", + "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d", + "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92", + "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4", + "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf", + "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6", + "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1", + "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4", + "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359", + "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3", + "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620", + "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512", + "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69", + "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2", + "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518", + "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0", + "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa", + "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4", + "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e", + "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1", + "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2" + ], + "index": "pypi", + "version": "==6.3.2" }, "decorator": { "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" ], "index": "pypi", - "version": "==4.4.2" + "version": "==5.1.1" + }, + "executing": { + "hashes": [ + "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501", + "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9" + ], + "version": "==0.8.3" }, "faker": { "hashes": [ - "sha256:bd56ab94b9ae45df3cdb6ef3ca3c2be2617e6a640699deb74394143d220faf04", - "sha256:d70983d5e623976e9e8987e9a39e8d55378e66049f393db35ea9a37b3190085f" + "sha256:0d5425894e098410b64aaade38a81074fa30163076251118523adf5bb44f8346", + "sha256:7ab2f741ef1c006ed7274a6ed75695ca8b610f78861566b599ce83c4953bf687" ], "index": "pypi", - "version": "==5.4.0" + "version": "==13.6.0" }, "iniconfig": { "hashes": [ @@ -1227,18 +1687,18 @@ }, "ipdb": { "hashes": [ - "sha256:c85398b5fb82f82399fc38c44fe3532c0dde1754abee727d8f5cfcc74547b334" + "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5" ], "index": "pypi", - "version": "==0.13.4" + "version": "==0.13.9" }, "ipython": { "hashes": [ - "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f", - "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a" + "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857", + "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1" ], "index": "pypi", - "version": "==7.19.0" + "version": "==8.2.0" }, "ipython-genutils": { "hashes": [ @@ -1250,35 +1710,43 @@ }, "jedi": { "hashes": [ - "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93", - "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707" + "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", + "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" ], "markers": "python_version >= '3.6'", - "version": "==0.18.0" + "version": "==0.18.1" + }, + "matplotlib-inline": { + "hashes": [ + "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee", + "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c" + ], + "markers": "python_version >= '3.5'", + "version": "==0.1.3" }, "more-itertools": { "hashes": [ - "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330", - "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf" + "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", + "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" ], "index": "pypi", - "version": "==8.6.0" + "version": "==8.12.0" }, "packaging": { "hashes": [ - "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858", - "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093" + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.8" + "markers": "python_version >= '3.6'", + "version": "==21.3" }, "parso": { "hashes": [ - "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410", - "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e" + "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", + "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" ], "markers": "python_version >= '3.6'", - "version": "==0.8.1" + "version": "==0.8.3" }, "pexpect": { "hashes": [ @@ -1296,30 +1764,37 @@ "index": "pypi", "version": "==0.7.5" }, + "pip": { + "hashes": [ + "sha256:b3a9de2c6ef801e9247d1527a4b16f92f2cc141cd1489f3fffaf6a9e96729764", + "sha256:c6aca0f2f081363f689f041d90dab2a07a9a07fb840284db2218117a52da800b" + ], + "markers": "python_version >= '3.7'", + "version": "==22.0.4" + }, "pipdeptree": { "hashes": [ - "sha256:0e4cbe3871287013162dbc0df8ae96fa748d4862160c932cbcbfd73260ff322a", - "sha256:44de04e0034b7d80a5071325c80c4c8f126da001225157f542f62afa79c60f8c", - "sha256:6899ba160bc7db98f0124d1aa6a680aa578adbac8558177ae66dd81bf69369de" + "sha256:2b97d80c64d229e01ad242f14229a899263c6e8645c588ec5b054c1b81f3065d", + "sha256:e20655a38d6e363d8e86d6a85e8a648680a3f4b6d039d6ee3ab0f539da1ad6ce" ], "index": "pypi", - "version": "==2.0.0" + "version": "==2.2.1" }, "pluggy": { "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], "index": "pypi", - "version": "==0.13.1" + "version": "==1.0.0" }, "prompt-toolkit": { "hashes": [ - "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7", - "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5" + "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752", + "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7" ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.10" + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.29" }, "ptyprocess": { "hashes": [ @@ -1329,53 +1804,68 @@ "index": "pypi", "version": "==0.7.0" }, + "pure-eval": { + "hashes": [ + "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", + "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" + ], + "version": "==0.2.2" + }, "py": { "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" ], "index": "pypi", - "version": "==1.10.0" + "version": "==1.11.0" }, "pygments": { "hashes": [ - "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716", - "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08" + "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", + "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" ], "index": "pypi", - "version": "==2.7.3" + "version": "==2.12.0" }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", + "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" ], "index": "pypi", - "version": "==2.4.7" + "version": "==3.0.8" }, "pytest": { "hashes": [ - "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8", - "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306" + "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", + "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" ], "index": "pypi", - "version": "==6.2.1" + "version": "==7.1.2" }, "pytest-cov": { "hashes": [ - "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191", - "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e" + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" ], "index": "pypi", - "version": "==2.10.1" + "version": "==3.0.0" }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.2" + }, + "setuptools": { + "hashes": [ + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + ], + "markers": "python_version >= '3.7'", + "version": "==62.1.0" }, "simplegeneric": { "hashes": [ @@ -1386,34 +1876,42 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "index": "pypi", - "version": "==1.15.0" + "version": "==1.16.0" }, - "text-unidecode": { + "stack-data": { "hashes": [ - "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", - "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12", + "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e" ], - "version": "==1.3" + "version": "==0.2.0" }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version > '3.6'", "version": "==0.10.2" }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, "traitlets": { "hashes": [ - "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", - "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" + "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7", + "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033" ], "index": "pypi", - "version": "==5.0.5" + "version": "==5.1.1" }, "wcwidth": { "hashes": [ diff --git a/app/__init__.py b/app/__init__.py index 7c0d23b9f..029f65ecf 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,13 +11,12 @@ from celery import Celery from flask import (Flask, abort, redirect, render_template, request as flask_request, session, url_for) from flask_bootstrap import Bootstrap -from flask_elasticsearch import FlaskElasticsearch from flask_login import LoginManager, current_user from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from flask_tracy import Tracy -from flask_wtf import CsrfProtect +from flask_wtf.csrf import CSRFProtect from flask_session import Session from raven.contrib.flask import Sentry @@ -25,11 +24,12 @@ from app.constants import OPENRECORDS_DL_EMAIL from app.lib import NYCHolidays, jinja_filters from config import Config, config +from elasticsearch import Elasticsearch bootstrap = Bootstrap() -es = FlaskElasticsearch() +es = Elasticsearch(Config.ELASTICSEARCH_HOST) db = SQLAlchemy() -csrf = CsrfProtect() +csrf = CSRFProtect() moment = Moment() mail = Mail() tracy = Tracy() @@ -101,9 +101,6 @@ def create_app(config_name='default'): app.jinja_env.filters['format_ultimate_determination_reason'] = jinja_filters.format_ultimate_determination_reason bootstrap.init_app(app) - es.init_app(app, - use_ssl=app.config['ELASTICSEARCH_USE_SSL'], - verify_certs=app.config['ELASTICSEARCH_VERIFY_CERTS']) db.init_app(app) csrf.init_app(app) moment.init_app(app) @@ -113,6 +110,7 @@ def create_app(config_name='default'): celery.config_from_object(celery_config) sentry.init_app(app, logging=app.config["USE_SENTRY"], level=logging.INFO) sess.init_app(app) + app.elasticsearch = Elasticsearch(Config.ELASTICSEARCH_HOST) with app.app_context(): from app.models import Anonymous diff --git a/app/admin/forms.py b/app/admin/forms.py index d6b18e136..7f0d7b6a0 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -1,5 +1,5 @@ from flask_login import current_user -from flask_wtf import Form +from flask_wtf import Form, FlaskForm from wtforms import ( SelectField, StringField, @@ -15,7 +15,7 @@ from app.models import Agencies -class ActivateAgencyUserForm(Form): +class ActivateAgencyUserForm(FlaskForm): users = SelectField('Add Agency Users') def __init__(self, agency_ein): @@ -25,7 +25,7 @@ def __init__(self, agency_ein): for u in Agencies.query.filter_by(ein=agency_ein).one().inactive_users] -class SelectAgencyForm(Form): +class SelectAgencyForm(FlaskForm): agencies = SelectField('Current Agency') def __init__(self, agency_ein=None): @@ -72,7 +72,7 @@ def __init__(self, agency_ein=None): # TODO: Add forms to modify agency_features (see models.py:183) -class AddAgencyUserForm(Form): +class AddAgencyUserForm(FlaskForm): agency = SelectField('Agency', choices=None, validators=[DataRequired()]) first_name = StringField('First Name', validators=[Length(max=32), DataRequired()]) last_name = StringField('Last Name', validators=[Length(max=64), DataRequired()]) diff --git a/app/agency/api/views.py b/app/agency/api/views.py index 0ffa2068c..a5db778e9 100644 --- a/app/agency/api/views.py +++ b/app/agency/api/views.py @@ -91,6 +91,7 @@ def get_custom_request_form_options(agency_ein): CustomRequestForms.category, CustomRequestForms.minimum_required).filter_by( agency_ein=agency_ein).order_by(asc(CustomRequestForms.category), asc(CustomRequestForms.id)).all() + custom_request_forms = [list(form) for form in custom_request_forms] return jsonify(custom_request_forms), 200 diff --git a/app/auth/forms.py b/app/auth/forms.py index 2ac0fd4dd..5da3df8a8 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -5,7 +5,7 @@ :synopsis: Defines the forms used to manage Authentication requests """ -from flask_wtf import Form +from flask_wtf import Form, FlaskForm from wtforms import StringField, SelectField, PasswordField, SubmitField from wtforms.validators import Length, Email, Optional, DataRequired @@ -13,7 +13,7 @@ from app.models import Agencies -class StripFieldsForm(Form): +class StripFieldsForm(FlaskForm): """ Any field data that can be stripped, will be stripped. http://stackoverflow.com/questions/26232165/automatically-strip-all-values-in-wtforms @@ -174,7 +174,7 @@ def validate(self): ) -class BasicLoginForm(Form): +class BasicLoginForm(FlaskForm): email = StringField("Email") password = PasswordField("Password") diff --git a/app/models.py b/app/models.py index 0b9dbc192..9eb3bf495 100644 --- a/app/models.py +++ b/app/models.py @@ -590,7 +590,6 @@ def es_update(self): es, actions, index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", chunk_size=current_app.config["ELASTICSEARCH_CHUNK_SIZE"], ) @@ -968,7 +967,6 @@ def es_update(self): if self.agency.is_active: es.update( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", id=self.id, body={ "doc": { @@ -1004,7 +1002,6 @@ def es_create(self): if current_app.config["ELASTICSEARCH_ENABLED"]: es.create( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", id=self.id, body={ "title": self.title, @@ -1040,7 +1037,6 @@ def es_delete(self): if current_app.config["ELASTICSEARCH_ENABLED"]: es.delete( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", id=self.id, ) diff --git a/app/report/forms.py b/app/report/forms.py index 41e5e6330..78a2e08bd 100644 --- a/app/report/forms.py +++ b/app/report/forms.py @@ -6,7 +6,7 @@ from datetime import date, timedelta from flask_login import current_user -from flask_wtf import Form +from flask_wtf import Form, FlaskForm from wtforms import DateField, SelectField, SubmitField from wtforms.validators import DataRequired @@ -14,7 +14,7 @@ from app.constants.dates import MONTHS, PORTAL_START_YEAR -class ReportFilterForm(Form): +class ReportFilterForm(FlaskForm): """ Form for users to filter different report statistics. @@ -37,7 +37,7 @@ def __init__(self): self.agency.choices.insert(1, self.agency.choices.pop(self.agency.choices.index(user_agency))) -class AcknowledgmentForm(Form): +class AcknowledgmentForm(FlaskForm): """Form to generate a report with acknowledgment data.""" date_from = DateField('Date From (required)', format='%m/%d/%Y', validators=[DataRequired()]) date_to = DateField('Date To (required)', format='%m/%d/%Y', validators=[DataRequired()]) @@ -60,7 +60,7 @@ def validate(self): return is_valid -class MonthlyMetricsReportForm(Form): +class MonthlyMetricsReportForm(FlaskForm): """Form to generate a monthly metrics report.""" year = SelectField('Year (required)', choices=None, validators=[DataRequired()]) month = SelectField('Month (required)', choices=MONTHS, validators=[DataRequired()]) @@ -76,7 +76,7 @@ def __init__(self): self.year.choices.insert(0, ('', '')) -class OpenDataReportForm(Form): +class OpenDataReportForm(FlaskForm): """Form to generate a report with Open Data compliance data.""" date_from = DateField('Date From (required)', id='open-data-date-from', format='%m/%d/%Y', validators=[DataRequired()]) date_to = DateField('Date To (required)', id='open-data-date-to', format='%m/%d/%Y', validators=[DataRequired()]) diff --git a/app/request/forms.py b/app/request/forms.py index 16a182f6d..bce1106b5 100644 --- a/app/request/forms.py +++ b/app/request/forms.py @@ -7,7 +7,7 @@ from datetime import datetime from flask_login import current_user -from flask_wtf import Form +from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import ( StringField, @@ -32,7 +32,7 @@ from app.lib.recaptcha_utils import Recaptcha3Field -class PublicUserRequestForm(Form): +class PublicUserRequestForm(FlaskForm): """ Form for public users to create a new FOIL request. For a public user, the required fields are: @@ -65,7 +65,7 @@ def __init__(self): self.request_agency.choices.insert(0, ("", "")) -class AgencyUserRequestForm(Form): +class AgencyUserRequestForm(FlaskForm): """ Form for agency users to create a new FOIL request. For an agency user, the required fields are: @@ -133,7 +133,7 @@ def __init__(self): self.request_agency.choices = current_user.agencies_for_forms() -class AnonymousRequestForm(Form): +class AnonymousRequestForm(FlaskForm): """ Form for anonymous users to create a new FOIL request. For a anonymous user, the required fields are: @@ -189,7 +189,7 @@ def __init__(self): self.request_agency.choices.insert(0, ("", "")) -class EditRequesterForm(Form): +class EditRequesterForm(FlaskForm): # TODO: Add class docstring email = StringField("Email") phone = StringField("Phone Number") @@ -220,7 +220,7 @@ def __init__(self, requester): self.zipcode.data = requester.mailing_address.get("zip") or "" -class DeterminationForm(Form): +class DeterminationForm(FlaskForm): # TODO: Add class docstring def __init__(self, agency_ein): super(DeterminationForm, self).__init__() @@ -309,7 +309,7 @@ class ReopenRequestForm(DeterminationForm): ultimate_determination_type = [determination_type.REOPENING] -class GenerateEnvelopeForm(Form): +class GenerateEnvelopeForm(FlaskForm): # TODO: Add class docstring template = SelectField("Template") recipient_name = StringField("Recipient Name") @@ -341,7 +341,7 @@ def __init__(self, agency_ein, requester): self.zipcode.data = requester.mailing_address.get("zip") or "" -class GenerateLetterForm(Form): +class GenerateLetterForm(FlaskForm): # TODO: Add class docstring def __init__(self, agency_ein): super(GenerateLetterForm, self).__init__() @@ -439,7 +439,7 @@ class GenerateResponseLetterForm(GenerateLetterForm): letter_type = [response_type.LETTER] -class SearchRequestsForm(Form): +class SearchRequestsForm(FlaskForm): # TODO: Add class docstring agency_ein = SelectField("Agency") agency_user = SelectField("User") @@ -507,7 +507,7 @@ def __init__(self): self.process() -class ContactAgencyForm(Form): +class ContactAgencyForm(FlaskForm): # TODO: Add class docstring first_name = StringField( u"First Name", validators=[InputRequired(), Length(max=32)] @@ -531,5 +531,5 @@ def __init__(self, request): self.subject.data = "Inquiry about {}".format(request.id) -class TechnicalSupportForm(Form): +class TechnicalSupportForm(FlaskForm): recaptcha = Recaptcha3Field(action="TestAction", execute_on_load=True) \ No newline at end of file diff --git a/app/request/utils.py b/app/request/utils.py index 046885519..9b975db56 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -248,6 +248,7 @@ def create_request(title, )) # 11. Create the elasticsearch request doc only if agency has been onboarded + print(agency_ein) agency = Agencies.query.filter_by(ein=agency_ein).one() # 12. Add all agency administrators to the request. diff --git a/app/request/views.py b/app/request/views.py index ca88334db..481e32753 100644 --- a/app/request/views.py +++ b/app/request/views.py @@ -20,7 +20,8 @@ from flask_login import current_user from sqlalchemy import any_ from sqlalchemy.orm.exc import NoResultFound -from werkzeug.utils import escape +# from werkzeug.utils import escape +from markupsafe import escape from app.constants import request_status, permission, HIDDEN_AGENCIES from app.lib.date_utils import DEFAULT_YEARS_HOLIDAY_LIST, get_holidays_date_list diff --git a/app/search/utils.py b/app/search/utils.py index 14628ea81..21ff1d967 100644 --- a/app/search/utils.py +++ b/app/search/utils.py @@ -38,7 +38,8 @@ def delete_index(): """ Delete all elasticsearch indices, ignoring errors. """ - es.indices.delete(current_app.config["ELASTICSEARCH_INDEX"], ignore=[400, 404]) + # es.indices.delete(current_app.config["ELASTICSEARCH_INDEX"], ignore=[400, 404]) + es.indices.delete(index=current_app.config["ELASTICSEARCH_INDEX"], ignore=[400, 404]) def delete_docs(): @@ -48,7 +49,6 @@ def delete_docs(): es.indices.refresh(index=current_app.config["ELASTICSEARCH_INDEX"]) es.delete_by_query( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", body={"query": {"match_all": {}}}, conflicts="proceed", wait_for_completion=True, @@ -64,51 +64,49 @@ def create_index(): index=current_app.config["ELASTICSEARCH_INDEX"], body={ "mappings": { - "request": { - "properties": { - "title": { - "type": "text", - "analyzer": "english", - "fields": { - # for sorting by title - "keyword": {"type": "keyword"} - }, + "properties": { + "title": { + "type": "text", + "analyzer": "english", + "fields": { + # for sorting by title + "keyword": {"type": "keyword"} }, - "description": {"type": "text", "analyzer": "english"}, - "agency_request_summary": { - "type": "text", - "analyzer": "english", - }, - "requester_id": {"type": "keyword"}, - "title_private": {"type": "boolean"}, - "agency_request_summary_private": {"type": "boolean"}, - "agency_ein": {"type": "keyword"}, - "agency_name": {"type": "keyword"}, - "agency_acronym": {"type": "keyword"}, - "status": {"type": "keyword"}, - "date_submitted": { - "type": "date", - "format": "strict_date_hour_minute_second", - }, - "date_due": { - "type": "date", - "format": "strict_date_hour_minute_second", - }, - "date_created": { - "type": "date", - "format": "strict_date_hour_minute_second", - }, - "date_received": { - "type": "date", - "format": "strict_date_hour_minute_second", - }, - "date_closed": { - "type": "date", - "format": "strict_date_hour_minute_second", - }, - "assigned_users": {"type": "keyword"}, - "request_type": {"type": "keyword"}, - } + }, + "description": {"type": "text", "analyzer": "english"}, + "agency_request_summary": { + "type": "text", + "analyzer": "english", + }, + "requester_id": {"type": "keyword"}, + "title_private": {"type": "boolean"}, + "agency_request_summary_private": {"type": "boolean"}, + "agency_ein": {"type": "keyword"}, + "agency_name": {"type": "keyword"}, + "agency_acronym": {"type": "keyword"}, + "status": {"type": "keyword"}, + "date_submitted": { + "type": "date", + "format": "strict_date_hour_minute_second", + }, + "date_due": { + "type": "date", + "format": "strict_date_hour_minute_second", + }, + "date_created": { + "type": "date", + "format": "strict_date_hour_minute_second", + }, + "date_received": { + "type": "date", + "format": "strict_date_hour_minute_second", + }, + "date_closed": { + "type": "date", + "format": "strict_date_hour_minute_second", + }, + "assigned_users": {"type": "keyword"}, + "request_type": {"type": "keyword"}, } } }, @@ -120,7 +118,7 @@ def create_docs(): Create elasticsearch request docs for every request db record. """ - agency_eins = {a.ein: a for a in Agencies.query.filter_by(is_active=True).all()} + agency_eins = {a.ein: a for a in Agencies.query.filter_by(is_active=True, ein='0860').all()} #: :type: collections.Iterable[app.models.Requests] requests = ( @@ -174,7 +172,6 @@ def create_docs(): es, operations, index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", chunk_size=current_app.config["ELASTICSEARCH_CHUNK_SIZE"], raise_on_error=True, ) @@ -416,7 +413,6 @@ def datestr_local_to_utc(datestr): if not for_csv: results = es.search( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", body=dsl, _source=[ "requester_id", @@ -447,7 +443,6 @@ def datestr_local_to_utc(datestr): else: results = es.search( index=current_app.config["ELASTICSEARCH_INDEX"], - doc_type="request", scroll="1m", body=dsl, _source=[ diff --git a/app/user/utils.py b/app/user/utils.py index bc10cd97d..edfd48371 100644 --- a/app/user/utils.py +++ b/app/user/utils.py @@ -222,6 +222,5 @@ def es_update_assigned_users(self, request_ids: list): es, actions, index=current_app.config['ELASTICSEARCH_INDEX'], - doc_type='request', chunk_size=current_app.config['ELASTICSEARCH_CHUNK_SIZE'] ) diff --git a/app/user_request/forms.py b/app/user_request/forms.py index 3824dc659..f968477ed 100644 --- a/app/user_request/forms.py +++ b/app/user_request/forms.py @@ -1,10 +1,10 @@ -from flask_wtf import Form +from flask_wtf import Form, FlaskForm from wtforms import SelectField, SelectMultipleField, BooleanField from app.constants import permission, role_name from app.models import Roles -class AddUserRequestForm(Form): +class AddUserRequestForm(FlaskForm): user = SelectField('Users', choices=None) permission = SelectMultipleField('Permissions', choices=None) roles = SelectField('Presets', choices=None) @@ -32,7 +32,7 @@ def __init__(self, assigned_users): ] -class EditUserRequestForm(Form): +class EditUserRequestForm(FlaskForm): user = SelectField('Users', choices=None) permission = SelectMultipleField('Permissions', choices=None) roles = SelectField('Presets', choices=None) @@ -60,7 +60,7 @@ def __init__(self, assigned_users): ] -class RemoveUserRequestForm(Form): +class RemoveUserRequestForm(FlaskForm): user = SelectField('Users') def __init__(self, assigned_users): From ba036b1f1bee89b6b383d6cde27869072f7b3869 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 4 May 2022 12:52:26 -0400 Subject: [PATCH 20/44] Fixed bugs from deprecated functions and code clean up --- app/admin/forms.py | 2 +- app/agency/api/views.py | 3 ++- app/auth/forms.py | 3 +-- app/models.py | 2 ++ app/report/forms.py | 2 +- app/report/views.py | 2 +- app/request/utils.py | 1 - app/request/views.py | 3 +-- app/search/utils.py | 3 +-- app/static/js/request/search.js | 6 +++--- app/templates/report/reports.html | 8 ++++---- app/user_request/forms.py | 2 +- 12 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/admin/forms.py b/app/admin/forms.py index 7f0d7b6a0..1c3d69274 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -1,5 +1,5 @@ from flask_login import current_user -from flask_wtf import Form, FlaskForm +from flask_wtf import FlaskForm from wtforms import ( SelectField, StringField, diff --git a/app/agency/api/views.py b/app/agency/api/views.py index a5db778e9..1f4acd5b4 100644 --- a/app/agency/api/views.py +++ b/app/agency/api/views.py @@ -91,7 +91,8 @@ def get_custom_request_form_options(agency_ein): CustomRequestForms.category, CustomRequestForms.minimum_required).filter_by( agency_ein=agency_ein).order_by(asc(CustomRequestForms.category), asc(CustomRequestForms.id)).all() - custom_request_forms = [list(form) for form in custom_request_forms] + # Convert the results of with_entities back to tuple format so that jsonify can be used + custom_request_forms = [tuple(form) for form in custom_request_forms] return jsonify(custom_request_forms), 200 diff --git a/app/auth/forms.py b/app/auth/forms.py index 5da3df8a8..727d4550f 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -5,12 +5,11 @@ :synopsis: Defines the forms used to manage Authentication requests """ -from flask_wtf import Form, FlaskForm +from flask_wtf import FlaskForm from wtforms import StringField, SelectField, PasswordField, SubmitField from wtforms.validators import Length, Email, Optional, DataRequired from app.constants import STATES -from app.models import Agencies class StripFieldsForm(FlaskForm): diff --git a/app/models.py b/app/models.py index 9eb3bf495..9d65af59a 100644 --- a/app/models.py +++ b/app/models.py @@ -527,6 +527,8 @@ def is_agency_active(self, ein=None): def agencies_for_forms(self): agencies = self.agencies.with_entities(Agencies.ein, Agencies._name).all() + # Convert the results of with_entities back to tuple format so that agencies can be processed + agencies = [tuple(agency) for agency in agencies] agencies.insert( 0, agencies.pop( diff --git a/app/report/forms.py b/app/report/forms.py index 78a2e08bd..f30da3e5c 100644 --- a/app/report/forms.py +++ b/app/report/forms.py @@ -6,7 +6,7 @@ from datetime import date, timedelta from flask_login import current_user -from flask_wtf import Form, FlaskForm +from flask_wtf import FlaskForm from wtforms import DateField, SelectField, SubmitField from wtforms.validators import DataRequired diff --git a/app/report/views.py b/app/report/views.py index 5c5861ce9..7366e8b3b 100644 --- a/app/report/views.py +++ b/app/report/views.py @@ -230,7 +230,7 @@ def open_data_report(): date_to_string = date_to.strftime('%Y%m%d') return send_file( BytesIO(open_data_report_spreadsheet), - attachment_filename='open_data_compliance_report_{}_{}.xls'.format(date_from_string, date_to_string), + attachment_filename='open_data_compliance_report_{}_{}.csv'.format(date_from_string, date_to_string), as_attachment=True ) else: diff --git a/app/request/utils.py b/app/request/utils.py index 9b975db56..046885519 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -248,7 +248,6 @@ def create_request(title, )) # 11. Create the elasticsearch request doc only if agency has been onboarded - print(agency_ein) agency = Agencies.query.filter_by(ein=agency_ein).one() # 12. Add all agency administrators to the request. diff --git a/app/request/views.py b/app/request/views.py index 481e32753..7eaf9a5ea 100644 --- a/app/request/views.py +++ b/app/request/views.py @@ -20,7 +20,6 @@ from flask_login import current_user from sqlalchemy import any_ from sqlalchemy.orm.exc import NoResultFound -# from werkzeug.utils import escape from markupsafe import escape from app.constants import request_status, permission, HIDDEN_AGENCIES @@ -80,7 +79,7 @@ def new(): kiosk_mode = eval_request_bool(escape(flask_request.args.get("kiosk_mode", False))) category = str(escape(flask_request.args.get("category", None))) agency = str(escape(flask_request.args.get("agency", None))) - title = str(escape(flask_request.args.get("title", None))) + title = str(escape(flask_request.args.get("title", ""))) if current_user.is_public: form = PublicUserRequestForm() diff --git a/app/search/utils.py b/app/search/utils.py index 21ff1d967..4b615d039 100644 --- a/app/search/utils.py +++ b/app/search/utils.py @@ -38,7 +38,6 @@ def delete_index(): """ Delete all elasticsearch indices, ignoring errors. """ - # es.indices.delete(current_app.config["ELASTICSEARCH_INDEX"], ignore=[400, 404]) es.indices.delete(index=current_app.config["ELASTICSEARCH_INDEX"], ignore=[400, 404]) @@ -118,7 +117,7 @@ def create_docs(): Create elasticsearch request docs for every request db record. """ - agency_eins = {a.ein: a for a in Agencies.query.filter_by(is_active=True, ein='0860').all()} + agency_eins = {a.ein: a for a in Agencies.query.filter_by(is_active=True).all()} #: :type: collections.Iterable[app.models.Requests] requests = ( diff --git a/app/static/js/request/search.js b/app/static/js/request/search.js index 6581b41ee..2d348d564 100644 --- a/app/static/js/request/search.js +++ b/app/static/js/request/search.js @@ -291,15 +291,15 @@ $(function () { success: function (data) { if (data.total !== 0) { noResultsFound = false; - results.html(buildResultsTable(data.results, data.count, data.total)); + results.html(buildResultsTable(data.results, data.count, data.total.value)); flask_moment_render_all(); $(".pagination").css("display", "flex"); pageInfo.text( (start + 1) + " - " + (start + data.count) + - " of " + (data.total).toLocaleString() + " of " + (data.total.value).toLocaleString() ); - total = data.total; + total = data.total.value; end = start + data.count; if (end === total) { next.attr("aria-disabled", true); diff --git a/app/templates/report/reports.html b/app/templates/report/reports.html index a6be23a33..c180cd867 100644 --- a/app/templates/report/reports.html +++ b/app/templates/report/reports.html @@ -44,11 +44,11 @@

FOIL Request Acknowledgement Status

{{ acknowledgment_form.csrf_token }}
{{ acknowledgment_form.date_from.label }} - {{ acknowledgment_form.date_from }} + {{ acknowledgment_form.date_from(type="text") }}
{{ acknowledgment_form.date_to.label }} - {{ acknowledgment_form.date_to(readonly=readonly) }} + {{ acknowledgment_form.date_to(readonly=readonly, type="text") }}
{{ acknowledgment_form.submit_field(class="btn btn-success") }} @@ -84,11 +84,11 @@

Open Data Compliance Report

{{ open_data_report_form.csrf_token }}
{{ open_data_report_form.date_from.label }} - {{ open_data_report_form.date_from }} + {{ open_data_report_form.date_from(type="text") }}
{{ open_data_report_form.date_to.label }} - {{ open_data_report_form.date_to }} + {{ open_data_report_form.date_to(type="text") }}
{{ open_data_report_form.submit_field(class="btn btn-success") }} diff --git a/app/user_request/forms.py b/app/user_request/forms.py index f968477ed..b0060898e 100644 --- a/app/user_request/forms.py +++ b/app/user_request/forms.py @@ -1,4 +1,4 @@ -from flask_wtf import Form, FlaskForm +from flask_wtf import FlaskForm from wtforms import SelectField, SelectMultipleField, BooleanField from app.constants import permission, role_name from app.models import Roles From b564dc5bacc804800848828f4036a4b0bb1dce0a Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 4 May 2022 14:59:02 -0400 Subject: [PATCH 21/44] Fixed custom request forms bug for radios with no selected value --- app/report/views.py | 2 +- app/static/js/request/main.js | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/report/views.py b/app/report/views.py index 7366e8b3b..5c5861ce9 100644 --- a/app/report/views.py +++ b/app/report/views.py @@ -230,7 +230,7 @@ def open_data_report(): date_to_string = date_to.strftime('%Y%m%d') return send_file( BytesIO(open_data_report_spreadsheet), - attachment_filename='open_data_compliance_report_{}_{}.csv'.format(date_from_string, date_to_string), + attachment_filename='open_data_compliance_report_{}_{}.xls'.format(date_from_string, date_to_string), as_attachment=True ) else: diff --git a/app/static/js/request/main.js b/app/static/js/request/main.js index c683da53f..d2dbc6842 100644 --- a/app/static/js/request/main.js +++ b/app/static/js/request/main.js @@ -772,17 +772,26 @@ function processCustomRequestFormData() { if ($("#" + this.id).prop("multiple") === true) { var selectMultipleId = "#" + this.id; - customRequestFormData[formKey]["form_fields"][fieldKey]["field_value"] = $(selectMultipleId).val(); - if ($(selectMultipleId).val() !== null) { + var selectMultipleValue = $(selectMultipleId).val(); + // set select multiple with no value to empty string so that field_value key is still created + if (selectMultipleValue == null) { + selectMultipleValue = ""; + } + customRequestFormData[formKey]["form_fields"][fieldKey]["field_value"] = selectMultipleValue; + if (selectMultipleValue !== "") { completedFields++; } } else if ($("#" + this.id).is(":radio") === true) { // since all radio inputs have the same id only take the value of the first one to avoid duplicates var radioValue = $("input[name='" + this.id + "']:checked").val(); + // set radios with no value to empty string so that field_value key is still created + if (radioValue == null) { + radioValue = ""; + } if (this.id !== previousRadioId) { customRequestFormData[formKey]["form_fields"][fieldKey]["field_value"] = radioValue; - if (radioValue != null) { + if (radioValue !== "") { completedFields++; } } else { From 305c3b63cc5620b1db1028c42c8d416aa2c9deb6 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 12 May 2022 17:16:50 -0400 Subject: [PATCH 22/44] Updated Pipfile --- .python-version | 2 +- Pipfile | 4 +- Pipfile.lock | 347 +++++++++++++++++++++++------------------------- 3 files changed, 165 insertions(+), 188 deletions(-) diff --git a/.python-version b/.python-version index 269aa9c86..7b59a5caa 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.8.3 +3.10.2 diff --git a/Pipfile b/Pipfile index 3010417c9..eec3d7152 100644 --- a/Pipfile +++ b/Pipfile @@ -29,7 +29,6 @@ py = "*" [packages] alembic = "*" -anyjson = "*" appdirs = "*" asn1crypto = "*" bcrypt = "*" @@ -45,7 +44,7 @@ cryptography = "*" cssselect2 = "*" defusedxml = "*" dominate = "*" -elasticsearch = "*" +elasticsearch = {version = ">=7.0,<8.0"} holidays = "*" html5lib = "*" idna = "*" @@ -83,7 +82,6 @@ business_calendar = "*" CairoSVG = "*" Flask = "*" Flask-Bootstrap = "*" -Flask-Elasticsearch = "*" Flask-Login = "*" Flask-Mail = "*" Flask-Migrate = "*" diff --git a/Pipfile.lock b/Pipfile.lock index f48cdbfba..25e88c622 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e25cd0829299ca5614d201515ced601ac7a8202f57c1ca4d1d166099d1112f4c" + "sha256": "f6c429e5ba00e653eeec86299c58dbb6021db4d200d2a595aa6b5d1894eedc4e" }, "pipfile-spec": 6, "requires": { @@ -32,13 +32,6 @@ "index": "pypi", "version": "==5.1.1" }, - "anyjson": { - "hashes": [ - "sha256:c7976a859d537cb22cfe130f4987e6a64e7c34edced8123153414d47cb11555d" - ], - "index": "pypi", - "version": "==0.2.5" - }, "appdirs": { "hashes": [ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", @@ -73,19 +66,20 @@ }, "bcrypt": { "hashes": [ - "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d", - "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", - "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", - "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", - "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7", - "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", - "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd", - "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", - "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", - "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" + "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", + "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", + "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", + "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", + "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", + "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", + "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", + "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", + "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", + "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", + "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" ], "index": "pypi", - "version": "==3.2.0" + "version": "==3.2.2" }, "billiard": { "hashes": [ @@ -298,18 +292,18 @@ }, "click": { "hashes": [ - "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", - "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" ], "index": "pypi", - "version": "==8.1.2" + "version": "==8.1.3" }, "click-didyoumean": { "hashes": [ "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" ], - "markers": "python_full_version >= '3.6.2' and python_version < '4'", + "markers": "python_version < '4' and python_full_version >= '3.6.2'", "version": "==0.3.0" }, "click-plugins": { @@ -383,31 +377,31 @@ }, "cryptography": { "hashes": [ - "sha256:0234bdb18620ed16bf186f0591aea0bbc321ecaf59c859d5f5cbe7b646d8377e", - "sha256:183d6a540659c6a729c08971f09f3fb1044c89dd5af9d6f18da824a071f5445d", - "sha256:1af4f31870ef2180aba1c04f6d957461a570c8cabcc4b5ac7fabf2b4a0364ea0", - "sha256:2d3d8a69d262ba27923466194bef637150aef286b11b160e087992206ac32f0c", - "sha256:2dfd682771c04c7e85a4b4ea6aa1682a3fd6f4d9845468fa6ba512b80a560a8d", - "sha256:4c52cb32ea0b9798234823d37c93cab8004c574b2d224f048cd5829d0639387b", - "sha256:57273f69b334c6d30f4d27abc7fb9c919ef4c6193af64420572808302bb45768", - "sha256:5a761fc1ff0eae360a80656bea462c3163dfaa8093b2fa0f72af929217b14a97", - "sha256:5c2517a2c58213ee62b36ee9ece4a710179ddb07db90e31d7619e7ea472c9dc3", - "sha256:710b9041fb97cc576e288b5f96583578ed352dd60608a402045405c388522b94", - "sha256:8921428ca6403d7eb52ee0e728e8b02601060a5791f6d64c8a3a12b5722064af", - "sha256:bc54780dd8f7236874ac29fc155c5cf811f7d910e5f0575932a38bdaac3b5146", - "sha256:bf893131cd79dc8eaf4940b3aa2f4a68eba050471f5deacfaedea6aab04f574f", - "sha256:cab59c774125596fa72f1decc5805894313b40f370a7c75597e37f0211027944", - "sha256:d119feb387ce2df9bfb92e5785df9094325cfa974e2e6aa08c8e4a8b56786afe", - "sha256:d426093df7f00de859bad45d6a09fdab9b8e4c6e46ea897dd0a302b94c7f6871", - "sha256:d886b2c9f8d1ab0916673bc3c89dd04fc6e6591861872c9f08402b0ab2843b82", - "sha256:d97479d943d549d4a78f044b0620a7d349191ed40933ffabff1cc5875e20682c", - "sha256:db1b9516e3072e0342287e06779bec84118bd780f794c8c07bd5da142a526103", - "sha256:e86734f28656f6fd5993ab32bd2d2680c3b8341d6f875faf5212bc78715db2a4", - "sha256:ebdc9c4b3577bb76b0defebe4ef8b866da5228a1c53fbbf394b7677fe292fee9", - "sha256:eee79c6c16949ed817c8cf288e6e124c4b8996e3312d9e7884c71cf9bdda212e" - ], - "index": "pypi", - "version": "==37.0.0" + "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804", + "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178", + "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717", + "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982", + "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004", + "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe", + "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452", + "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336", + "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4", + "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15", + "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d", + "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c", + "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0", + "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06", + "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9", + "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1", + "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023", + "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de", + "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f", + "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181", + "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e", + "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a" + ], + "index": "pypi", + "version": "==37.0.2" }, "cssselect2": { "hashes": [ @@ -456,28 +450,20 @@ "index": "pypi", "version": "==2.6.0" }, - "elastic-transport": { - "hashes": [ - "sha256:10914d0c5c268d9dcfee02cfbef861382d098309ba4eedab820062841bd214b3", - "sha256:869f7d668fb7738776639053fc87499caacbd1bdc7819f0de8025ac0e6cb29ce" - ], - "markers": "python_version >= '3.6'", - "version": "==8.1.2" - }, "elasticsearch": { "hashes": [ - "sha256:70d4ab4a4dcfc2631aaaf58853984b3e7658b85a490f4d2d2c657e91437bb620", - "sha256:d092f4a0d3f4228753cd06d4f70438aec101e13206b31425910d807ec23f232b" + "sha256:ccf5aaaaec7e7907abffe15a1b705ffacb8248a456c6ababd116bb58ebae2e2d", + "sha256:e7a03ef9b37a569f4d228a583b8aad3cf99aaae72a53cfc7f1fee70c7fa05255" ], "index": "pypi", - "version": "==8.1.3" + "version": "==7.17.3" }, "email-validator": { "hashes": [ - "sha256:2323219d19b82f887b64f2a84c6d73f451431bdf87744022c54b1b5bd0bde1bd", - "sha256:565fd3a7aa4516772f55732d50d34d0a18680b5f62995aea8b4a55b62c90c517" + "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8", + "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c" ], - "version": "==1.2.0" + "version": "==1.2.1" }, "filelock": { "hashes": [ @@ -489,11 +475,11 @@ }, "flask": { "hashes": [ - "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264", - "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8" + "sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477", + "sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe" ], "index": "pypi", - "version": "==2.1.1" + "version": "==2.1.2" }, "flask-bootstrap": { "hashes": [ @@ -502,20 +488,13 @@ "index": "pypi", "version": "==3.3.7.1" }, - "flask-elasticsearch": { - "hashes": [ - "sha256:5f288c275c3865532c6c8af71c1153c05c6d56a45ee1a7faf43dc49acc5f4a2f" - ], - "index": "pypi", - "version": "==0.2.5" - }, "flask-login": { "hashes": [ - "sha256:5cb01ce4dc253967b6ac722a11e46de83b6272ef7a19cc7b5725ae636916d04d", - "sha256:aa84fcfb4c3cf09ca58c08e816b7bce73f1349ba1cf13d00d8dffc5872d5fcf6" + "sha256:1306d474a270a036d6fd14f45640c4d77355e4f1c67ca4331b372d3448997b8c", + "sha256:b9a4287a2d0067a7a482a23e40075e0d670f371974633fe890222dece4e02a74" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.6.1" }, "flask-mail": { "hashes": [ @@ -663,7 +642,7 @@ "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" ], - "markers": "python_version >= '3' and (platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32'))))))", + "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "version": "==1.1.2" }, "gunicorn": { @@ -724,19 +703,19 @@ }, "jinja2": { "hashes": [ - "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", - "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" ], "index": "pypi", - "version": "==3.1.1" + "version": "==3.1.2" }, "jsonschema": { "hashes": [ - "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83", - "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823" + "sha256:71b5e39324422543546572954ce71c67728922c104902cb7ce252e522235b33f", + "sha256:7c6d882619340c3347a1bf7315e147e6d3dae439033ae6383d6acb908c101dfc" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.5.1" }, "kombu": { "hashes": [ @@ -1122,11 +1101,11 @@ }, "pyparsing": { "hashes": [ - "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", - "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" ], "index": "pypi", - "version": "==3.0.8" + "version": "==3.0.9" }, "pyphen": { "hashes": [ @@ -1234,11 +1213,11 @@ }, "redis": { "hashes": [ - "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f", - "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2" + "sha256:84316970995a7adb907a56754d2b92d88fc2d252963dc5ac34c88f0f1a22c25d", + "sha256:94b617b4cd296e94991146f66fc5559756fbefe9493604f0312e4d3298ac63e9" ], "index": "pypi", - "version": "==4.2.2" + "version": "==4.3.1" }, "requests": { "hashes": [ @@ -1258,11 +1237,11 @@ }, "setuptools": { "hashes": [ - "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", - "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + "sha256:5534570b9980fc650d45c62877ff603c7aaaf24893371708736cc016bd221c3c", + "sha256:ca6ba73b7fd5f734ae70ece8c4c1f7062b07f3352f6428f6277e27c8f5c64237" ], "markers": "python_version >= '3.7'", - "version": "==62.1.0" + "version": "==62.2.0" }, "simplekv": { "hashes": [ @@ -1395,11 +1374,11 @@ }, "weasyprint": { "hashes": [ - "sha256:12260f1be1052e5c5b6e8bc90069969c6ee7ad77178e02398763ced5ea2cbc11", - "sha256:e04da040630566c44caa202033f07f858765f536709045a809739f3d048e89d6" + "sha256:6a5008b3e1152498f206b2d0791b3e161f507607e31ca42b307cb31b49795462", + "sha256:ea5d5f2f159262e38b6e85939d8510e9735a47751a9647c9eaa93c22ced86230" ], "index": "pypi", - "version": "==54.3" + "version": "==55.0" }, "webencodings": { "hashes": [ @@ -1411,81 +1390,81 @@ }, "werkzeug": { "hashes": [ - "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6", - "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74" + "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6", + "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255" ], "index": "pypi", - "version": "==2.1.1" + "version": "==2.1.2" }, "wrapt": { "hashes": [ - "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b", - "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0", - "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330", - "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3", - "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68", - "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa", - "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe", - "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd", - "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b", - "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80", - "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38", - "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f", - "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350", - "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd", - "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb", - "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3", - "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0", - "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff", - "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c", - "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758", - "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036", - "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb", - "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763", - "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9", - "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7", - "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1", - "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7", - "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0", - "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5", - "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce", - "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8", - "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279", - "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0", - "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06", - "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561", - "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a", - "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311", - "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131", - "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4", - "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291", - "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4", - "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8", - "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8", - "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d", - "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c", - "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd", - "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d", - "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6", - "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775", - "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e", - "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627", - "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e", - "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8", - "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1", - "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48", - "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc", - "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3", - "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6", - "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425", - "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d", - "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23", - "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c", - "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33", - "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653" + "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", + "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", + "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", + "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", + "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", + "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", + "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", + "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", + "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", + "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", + "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", + "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", + "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", + "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", + "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", + "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", + "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", + "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", + "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", + "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", + "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", + "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", + "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", + "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", + "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", + "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", + "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", + "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", + "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", + "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", + "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", + "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", + "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", + "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", + "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", + "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", + "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", + "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", + "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", + "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", + "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", + "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", + "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", + "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", + "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", + "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", + "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", + "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", + "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", + "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", + "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", + "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", + "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", + "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", + "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", + "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", + "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", + "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", + "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", + "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", + "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", + "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", + "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", + "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.14.0" + "version": "==1.14.1" }, "wtforms": { "extras": [ @@ -1672,11 +1651,11 @@ }, "faker": { "hashes": [ - "sha256:0d5425894e098410b64aaade38a81074fa30163076251118523adf5bb44f8346", - "sha256:7ab2f741ef1c006ed7274a6ed75695ca8b610f78861566b599ce83c4953bf687" + "sha256:7b25b2b980d3f0e61c586ec6365a39c797bae095f594890cc7bfb6f5ee8e66b4", + "sha256:f1b6dccdd57261918830b974a7cfa5b6a9044cf05d17d57bcbc757e0220db56f" ], "index": "pypi", - "version": "==13.6.0" + "version": "==13.11.0" }, "iniconfig": { "hashes": [ @@ -1694,11 +1673,11 @@ }, "ipython": { "hashes": [ - "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857", - "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1" + "sha256:341456643a764c28f670409bbd5d2518f9b82c013441084ff2c2fc999698f83b", + "sha256:807ae3cf43b84693c9272f70368440a9a7eaa2e7e6882dad943c32fbf7e51402" ], "index": "pypi", - "version": "==8.2.0" + "version": "==8.3.0" }, "ipython-genutils": { "hashes": [ @@ -1726,11 +1705,11 @@ }, "more-itertools": { "hashes": [ - "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", - "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" + "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f", + "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb" ], "index": "pypi", - "version": "==8.12.0" + "version": "==8.13.0" }, "packaging": { "hashes": [ @@ -1766,11 +1745,11 @@ }, "pip": { "hashes": [ - "sha256:b3a9de2c6ef801e9247d1527a4b16f92f2cc141cd1489f3fffaf6a9e96729764", - "sha256:c6aca0f2f081363f689f041d90dab2a07a9a07fb840284db2218117a52da800b" + "sha256:2debf847016cfe643fa1512e2d781d3ca9e5c878ba0652583842d50cc2bcc605", + "sha256:802e797fb741be1c2d475533d4ea951957e4940091422bd4a24848a7ac95609d" ], "markers": "python_version >= '3.7'", - "version": "==22.0.4" + "version": "==22.1" }, "pipdeptree": { "hashes": [ @@ -1829,11 +1808,11 @@ }, "pyparsing": { "hashes": [ - "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", - "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" ], "index": "pypi", - "version": "==3.0.8" + "version": "==3.0.9" }, "pytest": { "hashes": [ @@ -1861,11 +1840,11 @@ }, "setuptools": { "hashes": [ - "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", - "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + "sha256:5534570b9980fc650d45c62877ff603c7aaaf24893371708736cc016bd221c3c", + "sha256:ca6ba73b7fd5f734ae70ece8c4c1f7062b07f3352f6428f6277e27c8f5c64237" ], "markers": "python_version >= '3.7'", - "version": "==62.1.0" + "version": "==62.2.0" }, "simplegeneric": { "hashes": [ @@ -1894,7 +1873,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version > '3.6'", + "markers": "python_version >= '3.7'", "version": "==0.10.2" }, "tomli": { @@ -1907,11 +1886,11 @@ }, "traitlets": { "hashes": [ - "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7", - "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033" + "sha256:60474f39bf1d39a11e0233090b99af3acee93bbc2281777e61dd8c87da8a0014", + "sha256:9dd4025123fbe018a2092b2ad6984792f53ea3362c698f37473258b1fa97b0bc" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.2.0" }, "wcwidth": { "hashes": [ From d7796e13e9f1e2d83ebc3d292598d8cd95a78ed3 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Tue, 9 Aug 2022 16:34:34 -0400 Subject: [PATCH 23/44] Added support for Azure storage on file uploads WIP --- app/lib/file_utils.py | 26 ++++++++++++- app/request/utils.py | 21 +++++++++-- app/response/views.py | 88 ++++++++++++++++++++++--------------------- config.py | 5 +++ 4 files changed, 92 insertions(+), 48 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 919b442c4..dafdd0bc6 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -16,6 +16,7 @@ from contextlib import contextmanager from flask import current_app, send_from_directory from app import sentry +from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient TRANSFER_SIZE_LIMIT = 512000 # 512 kb @@ -246,5 +247,28 @@ def os_get_hash(path): @_sftp_switch(_sftp_send_file) def send_file(directory, filename, **kwargs): path = _get_file_serving_path(directory, filename) - shutil.copy(os.path.join(directory, filename), path) + # Move file to data directory if SFTP enabled + if current_app.config['USE_SFTP']: + shutil.copy(os.path.join(directory, filename), path) + + # Download file from Azure if Azure storage is enabled + if current_app.config['USE_AZURE_STORAGE']: + azure_download(path, os.path.join(directory, filename)) return send_from_directory(*os.path.split(path), **kwargs) + + +def azure_upload(tmp_path, blob_name): + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + with open(tmp_path, 'rb') as data: + blob_client.upload_blob(data) + + +def azure_download(tmp_path, blob_name): + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + with open(tmp_path, 'wb') as data: + download_stream = blob_client.download_blob() + data.write(download_stream.readall()) \ No newline at end of file diff --git a/app/request/utils.py b/app/request/utils.py index 046885519..398b45500 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -181,6 +181,11 @@ def create_request(title, )) if upload_path is not None: + # Store file metadata + file_mimetype = fu.get_mime_type(upload_path) + file_size = fu.getsize(upload_path) + file_hash = fu.get_hash(upload_path) + # 7. Move file to upload directory upload_path = _move_validated_upload(request_id, upload_path) # 8. Create response object @@ -189,9 +194,9 @@ def create_request(title, RELEASE_AND_PRIVATE, filename, filename, - fu.get_mime_type(upload_path), - fu.getsize(upload_path), - fu.get_hash(upload_path), + file_mimetype, + file_size, + file_hash, is_editable=False) create_object(obj=response) @@ -366,7 +371,15 @@ def _move_validated_upload(request_id, tmp_path): valid_path = os.path.join(dst_dir, valid_name) # store file metadata in redis redis_set_file_metadata(request_id, tmp_path) - fu.move(tmp_path, valid_path) + + # Move file to data directory if SFTP enabled + if current_app.config['USE_SFTP']: + fu.move(tmp_path, valid_path) + + # Upload file to Azure if Azure storage is enabled + if current_app.config['USE_AZURE_STORAGE']: + fu.azure_upload(tmp_path, valid_path) + upload_redis.set( get_upload_key(request_id, valid_name), upload_status.READY) diff --git a/app/response/views.py b/app/response/views.py index 17cabc5ef..fdd9f3437 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -717,55 +717,57 @@ def get_response_content(response_id): response_.name ) token = flask_request.args.get('token') - if fu.exists(filepath): - if response_.is_public: - # then we just serve the file, anyone can view it - @after_this_request - def remove(resp): - os.remove(serving_path) - return resp - - return fu.send_file(*filepath_parts, as_attachment=True) - else: - # check presence of token in url - if token is not None: - resptok = ResponseTokens.query.filter_by( - token=token, response_id=response_id).first() - if resptok is not None: - if response_.privacy != PRIVATE: - @after_this_request - def remove(resp): - os.remove(serving_path) - return resp - - return fu.send_file(*filepath_parts, as_attachment=True) - else: - delete_object(resptok) - - # if token not included, nonexistent, or is expired, but user is logged in - if current_user.is_authenticated: - # user is agency or is public and response is not private - if (((current_user.is_public and response_.privacy != PRIVATE) - or current_user.is_agency) - # user is associated with request - and UserRequests.query.filter_by( - request_id=response_.request_id, - user_guid=current_user.guid - ).first() is not None): + if current_app.config['USE_SFTP'] and not fu.exists(filepath): + return abort(403) + + if response_.is_public: + # then we just serve the file, anyone can view it + @after_this_request + def remove(resp): + os.remove(serving_path) + return resp + + return fu.send_file(*filepath_parts, as_attachment=True) + else: + # check presence of token in url + if token is not None: + resptok = ResponseTokens.query.filter_by( + token=token, response_id=response_id).first() + if resptok is not None: + if response_.privacy != PRIVATE: @after_this_request def remove(resp): os.remove(serving_path) return resp return fu.send_file(*filepath_parts, as_attachment=True) - # user does not have permission to view file - return abort(403) - else: - # redirect to login - return redirect(login_url( - login_manager.login_view, - next_url=url_for('request.view', request_id=response_.request_id) - )) + else: + delete_object(resptok) + + # if token not included, nonexistent, or is expired, but user is logged in + if current_user.is_authenticated: + # user is agency or is public and response is not private + if (((current_user.is_public and response_.privacy != PRIVATE) + or current_user.is_agency) + # user is associated with request + and UserRequests.query.filter_by( + request_id=response_.request_id, + user_guid=current_user.guid + ).first() is not None): + @after_this_request + def remove(resp): + os.remove(serving_path) + return resp + + return fu.send_file(*filepath_parts, as_attachment=True) + # user does not have permission to view file + return abort(403) + else: + # redirect to login + return redirect(login_url( + login_manager.login_view, + next_url=url_for('request.view', request_id=response_.request_id) + )) return abort(404) # file does not exist diff --git a/config.py b/config.py index 7b0e3d5e0..2153a8d8b 100644 --- a/config.py +++ b/config.py @@ -169,6 +169,11 @@ class Config: SENTRY_DSN = os.environ.get('SENTRY_DSN') USE_SENTRY = os.environ.get('USE_SENTRY') == "True" + # Azure Settings + USE_AZURE_STORAGE = os.environ.get('USE_AZURE_STORAGE') or False + AZURE_STORAGE_CONNECTION_STRING = os.environ.get('AZURE_STORAGE_CONNECTION_STRING') + AZURE_STORAGE_CONTAINER = os.environ.get('AZURE_STORAGE_CONTAINER') + @staticmethod def init_app(app): pass From 65cee088a1e84e8bd614da12e90c0522d3cda3f5 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Tue, 23 Aug 2022 16:12:31 -0400 Subject: [PATCH 24/44] Fixed file response uploads --- app/lib/file_utils.py | 22 ++++++- app/upload/utils.py | 12 ++-- app/upload/views.py | 149 +++++++++++++++++++++--------------------- 3 files changed, 104 insertions(+), 79 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index dafdd0bc6..63120aef1 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -257,6 +257,12 @@ def send_file(directory, filename, **kwargs): return send_from_directory(*os.path.split(path), **kwargs) +def create_azure_blob_client(blob_name): + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + return blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + + def azure_upload(tmp_path, blob_name): connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') blob_service_client = BlobServiceClient.from_connection_string(connection_string) @@ -271,4 +277,18 @@ def azure_download(tmp_path, blob_name): blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) with open(tmp_path, 'wb') as data: download_stream = blob_client.download_blob() - data.write(download_stream.readall()) \ No newline at end of file + data.write(download_stream.readall()) + + +def azure_exists(blob_name): + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + return blob_client.exists() + + +def azure_delete(blob_name): + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + blob_client.delete_blob() \ No newline at end of file diff --git a/app/upload/utils.py b/app/upload/utils.py index 0037a792e..3001333f5 100644 --- a/app/upload/utils.py +++ b/app/upload/utils.py @@ -176,11 +176,13 @@ def scan_and_complete_upload(request_id, filepath, is_update=False, response_id= # in the time between the call to fu.exists # and fu.makedirs, the directory was created current_app.logger.error("OS Error: {}".format(e.args)) - - fu.move( - filepath, - os.path.join(dst_dir, filename) - ) + if current_app.config['USE_SFTP']: + fu.move( + filepath, + os.path.join(dst_dir, filename) + ) + elif current_app.config['USE_AZURE_STORAGE']: + fu.azure_upload(filepath, os.path.join(dst_dir, filename)) redis.set(key, upload_status.READY) diff --git a/app/upload/views.py b/app/upload/views.py index b583d92f8..60c32e4d1 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -71,90 +71,90 @@ def post(request_id): } """ files = request.files - file_ = files[next(files.keys())] - filename = secure_filename(file_.filename) - is_update = eval_request_bool(request.form.get('update')) - agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein - if is_allowed(user=current_user, request_id=request_id, permission=permission.ADD_FILE) or \ - is_allowed(user=current_user, request_id=request_id, permission=permission.EDIT_FILE): - response_id = request.form.get('response_id') if is_update else None - if upload_exists(request_id, filename, response_id): - response = { - "files": [{ - "name": filename, - "error": "A file with this name has already " - "been uploaded for this request." - # TODO: "link": ? would be nice - }] - } - else: - upload_path = os.path.join( - current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], - request_id) - if not os.path.exists(upload_path): - os.mkdir(upload_path) - filepath = os.path.join(upload_path, filename) - key = get_upload_key(request_id, filename, is_update) + for key, file_ in files.items(): + filename = secure_filename(file_.filename) + is_update = eval_request_bool(request.form.get('update')) + agency_ein = Requests.query.filter_by(id=request_id).one().agency.ein + if is_allowed(user=current_user, request_id=request_id, permission=permission.ADD_FILE) or \ + is_allowed(user=current_user, request_id=request_id, permission=permission.EDIT_FILE): + response_id = request.form.get('response_id') if is_update else None + if upload_exists(request_id, filename, response_id): + response = { + "files": [{ + "name": filename, + "error": "A file with this name has already " + "been uploaded for this request." + # TODO: "link": ? would be nice + }] + } + else: + upload_path = os.path.join( + current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], + request_id) + if not os.path.exists(upload_path): + os.mkdir(upload_path) + filepath = os.path.join(upload_path, filename) + key = get_upload_key(request_id, filename, is_update) + + try: + if CONTENT_RANGE_HEADER in request.headers: + start, size = parse_content_range( + request.headers[CONTENT_RANGE_HEADER]) - try: - if CONTENT_RANGE_HEADER in request.headers: - start, size = parse_content_range( - request.headers[CONTENT_RANGE_HEADER]) + # Only validate mime type on first chunk + valid_file_type = True + file_type = None + if start == 0: + valid_file_type, file_type = is_valid_file_type(file_) + if current_user.is_agency_active(agency_ein): + valid_file_type = True + if os.path.exists(filepath): + # remove existing file (upload 'restarted' for same file) + os.remove(filepath) - # Only validate mime type on first chunk - valid_file_type = True - file_type = None - if start == 0: + if valid_file_type: + redis.set(key, upload_status.PROCESSING) + with open(filepath, 'ab') as fp: + fp.seek(start) + fp.write(file_.stream.read()) + # scan if last chunk written + if os.path.getsize(filepath) == size: + scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) + else: valid_file_type, file_type = is_valid_file_type(file_) if current_user.is_agency_active(agency_ein): valid_file_type = True - if os.path.exists(filepath): - # remove existing file (upload 'restarted' for same file) - os.remove(filepath) - - if valid_file_type: - redis.set(key, upload_status.PROCESSING) - with open(filepath, 'ab') as fp: - fp.seek(start) - fp.write(file_.stream.read()) - # scan if last chunk written - if os.path.getsize(filepath) == size: + if valid_file_type: + redis.set(key, upload_status.PROCESSING) + file_.save(filepath) scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) - else: - valid_file_type, file_type = is_valid_file_type(file_) - if current_user.is_agency_active(agency_ein): - valid_file_type = True - if valid_file_type: - redis.set(key, upload_status.PROCESSING) - file_.save(filepath) - scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) - if not valid_file_type: + if not valid_file_type: + response = { + "files": [{ + "name": filename, + "error": "The file type '{}' is not allowed.".format( + file_type) + }] + } + else: + response = { + "files": [{ + "name": filename, + "original_name": file_.filename, + "size": os.path.getsize(filepath), + }] + } + except Exception as e: + sentry.captureException() + redis.set(key, upload_status.ERROR) + current_app.logger.exception("Upload for file '{}' failed: {}".format(filename, e)) response = { "files": [{ "name": filename, - "error": "The file type '{}' is not allowed.".format( - file_type) + "error": "There was a problem uploading this file." }] } - else: - response = { - "files": [{ - "name": filename, - "original_name": file_.filename, - "size": os.path.getsize(filepath), - }] - } - except Exception as e: - sentry.captureException() - redis.set(key, upload_status.ERROR) - current_app.logger.exception("Upload for file '{}' failed: {}".format(filename, e)) - response = { - "files": [{ - "name": filename, - "error": "There was a problem uploading this file." - }] - } return jsonify(response), 200 @@ -233,9 +233,12 @@ def delete(r_id_type, r_id, filecode): os.remove(filepath) found = True else: - if fu.exists(filepath): + if current_app.config['USE_SFTP'] and fu.exists(filepath): fu.remove(filepath) found = True + elif current_app.config['USE_AZURE_STORAGE'] and fu.azure_exists(filepath): + fu.azure_delete(filepath) + found = True if found: response = {"deleted": filename} else: From b89f11906ace4cf2d18182dca10f56ca7e8dd6a0 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Tue, 6 Sep 2022 15:04:58 -0400 Subject: [PATCH 25/44] File response edit and delete for azure storage --- app/lib/file_utils.py | 47 ++++++++++++++++++++----- app/request/utils.py | 2 +- app/response/utils.py | 82 ++++++++++++++++++++++++++++++------------- app/response/views.py | 6 ++++ app/upload/utils.py | 27 ++++++++------ app/upload/views.py | 4 ++- config.py | 3 ++ 7 files changed, 127 insertions(+), 44 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 63120aef1..783be61ca 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -16,7 +16,13 @@ from contextlib import contextmanager from flask import current_app, send_from_directory from app import sentry -from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient +from azure.storage.blob import (generate_blob_sas, + BlobSasPermissions, + BlobServiceClient, + BlobClient, + ContainerClient + ) +from datetime import datetime, timedelta TRANSFER_SIZE_LIMIT = 512000 # 512 kb @@ -247,7 +253,7 @@ def os_get_hash(path): @_sftp_switch(_sftp_send_file) def send_file(directory, filename, **kwargs): path = _get_file_serving_path(directory, filename) - # Move file to data directory if SFTP enabled + # Move file to data directory if volume storage enabled if current_app.config['USE_SFTP']: shutil.copy(os.path.join(directory, filename), path) @@ -263,19 +269,20 @@ def create_azure_blob_client(blob_name): return blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) -def azure_upload(tmp_path, blob_name): +def azure_upload(source_path, blob_name): connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) - with open(tmp_path, 'rb') as data: - blob_client.upload_blob(data) + with open(source_path, 'rb') as data: + blob_client.upload_blob(data, overwrite=True) + os.remove(source_path) -def azure_download(tmp_path, blob_name): +def azure_download(source_path, blob_name): connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) - with open(tmp_path, 'wb') as data: + with open(source_path, 'wb') as data: download_stream = blob_client.download_blob() data.write(download_stream.readall()) @@ -291,4 +298,28 @@ def azure_delete(blob_name): connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') blob_service_client = BlobServiceClient.from_connection_string(connection_string) blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) - blob_client.delete_blob() \ No newline at end of file + blob_client.delete_blob() + + +def azure_copy(current_blob_name, new_blob_name): + # Generate SAS token + sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + account_key=current_app.config['AZURE_STORAGE_ACCOUNT_KEY'], + container_name=current_app.config['AZURE_STORAGE_CONTAINER'], + blob_name=current_blob_name, + permission=BlobSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=1)) + + # Generate blob URL + url = "https://{0}.blob.core.windows.net/{1}/{2}?{3}".format( + current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + current_app.config['AZURE_STORAGE_CONTAINER'], + current_blob_name, + sas_token) + + # Copy blob to new location + connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], + blob=new_blob_name) + blob_client.start_copy_from_url(url) \ No newline at end of file diff --git a/app/request/utils.py b/app/request/utils.py index 398b45500..704974f1a 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -372,7 +372,7 @@ def _move_validated_upload(request_id, tmp_path): # store file metadata in redis redis_set_file_metadata(request_id, tmp_path) - # Move file to data directory if SFTP enabled + # Move file to data directory if volume storage is enabled if current_app.config['USE_SFTP']: fu.move(tmp_path, valid_path) diff --git a/app/response/utils.py b/app/response/utils.py index 712ebe987..3c622b94d 100644 --- a/app/response/utils.py +++ b/app/response/utils.py @@ -87,7 +87,7 @@ EnvelopeTemplates ) from app.request.api.utils import create_request_info_event - +from app.upload.utils import complete_upload # TODO: class ResponseProducer() @@ -2789,7 +2789,12 @@ def set_edited_data(self): UPDATED_FILE_DIRNAME, new_filename ) - if fu.exists(filepath): + quarantine_path = os.path.join( + current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], + self.response.request_id, + new_filename + ) + if fu.exists(filepath) or fu.exists(quarantine_path): try: # fetch file metadata from redis store size, mime_type, hash_ = redis_get_file_metadata( @@ -2815,14 +2820,14 @@ def set_edited_data(self): hash_) if self.update: redis_delete_file_metadata(self.response.id, filepath, is_update=True) - self.replace_old_file(filepath) + self.replace_old_file(filepath, new_filename) else: self.errors.append( "File '{}' not found.".format(new_filename)) if self.update: self.handle_response_token(bool(new_filename)) - def replace_old_file(self, updated_filepath): + def replace_old_file(self, updated_filepath, new_filename): """ Move the new file out of the 'updated' directory and delete the file it is replacing. @@ -2831,19 +2836,34 @@ def replace_old_file(self, updated_filepath): current_app.config['UPLOAD_DIRECTORY'], self.response.request_id ) - fu.remove( - os.path.join( - upload_path, - self.response.name + if current_app.config['USE_SFTP']: + fu.remove( + os.path.join( + upload_path, + self.response.name + ) ) - ) - fu.rename( - updated_filepath, - os.path.join( - upload_path, - os.path.basename(updated_filepath) + fu.rename( + updated_filepath, + os.path.join( + upload_path, + os.path.basename(updated_filepath) + ) ) - ) + if current_app.config['USE_AZURE_STORAGE']: + fu.azure_delete( + os.path.join( + upload_path, + self.response.name + ) + ) + quarantine_path = os.path.join( + current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], + self.response.request_id, + new_filename + ) + complete_upload(self.response.request_id, quarantine_path, new_filename) + def handle_response_token(self, file_changed): """ @@ -2930,18 +2950,32 @@ def move_deleted_file(self): DELETED_FILE_DIRNAME, str(self.response.id) ) - if not fu.exists(dir_deleted): - fu.makedirs(dir_deleted) - fu.rename( - os.path.join( - upload_path, - self.response.name - ), - os.path.join( + if current_app.config['USE_SFTP']: + if not fu.exists(dir_deleted): + fu.makedirs(dir_deleted) + fu.rename( + os.path.join( + upload_path, + self.response.name + ), + os.path.join( + dir_deleted, + self.response.name + ) + ) + if current_app.config['USE_AZURE_STORAGE']: + deleted_filename = os.path.join( dir_deleted, self.response.name ) - ) + current_blob_name = os.path.join(upload_path, self.response.name) + fu.azure_copy(current_blob_name, deleted_filename) + fu.azure_delete( + os.path.join( + upload_path, + self.response.name + ) + ) class RespNoteEditor(ResponseEditor): diff --git a/app/response/views.py b/app/response/views.py index fdd9f3437..93894a6c1 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -78,6 +78,7 @@ RespInstructionsEditor, RespLinkEditor ) +from app.upload.utils import complete_upload @response.route('/note/', methods=['POST']) @@ -147,6 +148,11 @@ def response_file(request_id): release_private_links = [] private_links = [] for file_data in files: + quarantine_path = os.path.join( + current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], + request_id, + file_data) + complete_upload(current_request.id, quarantine_path, file_data) response_obj = add_file(current_request.id, file_data, files[file_data]['title'], diff --git a/app/upload/utils.py b/app/upload/utils.py index 3001333f5..392638222 100644 --- a/app/upload/utils.py +++ b/app/upload/utils.py @@ -168,24 +168,31 @@ def scan_and_complete_upload(request_id, filepath, is_update=False, response_id= ) # store file metadata in redis redis_set_file_metadata(response_id or request_id, filepath, is_update) - if not fu.exists(dst_dir): - try: - fu.makedirs(dst_dir) - except OSError as e: - sentry.captureException() - # in the time between the call to fu.exists - # and fu.makedirs, the directory was created - current_app.logger.error("OS Error: {}".format(e.args)) if current_app.config['USE_SFTP']: + if not fu.exists(dst_dir): + try: + fu.makedirs(dst_dir) + except OSError as e: + sentry.captureException() + # in the time between the call to fu.exists + # and fu.makedirs, the directory was created + current_app.logger.error("OS Error: {}".format(e.args)) fu.move( filepath, os.path.join(dst_dir, filename) ) - elif current_app.config['USE_AZURE_STORAGE']: - fu.azure_upload(filepath, os.path.join(dst_dir, filename)) redis.set(key, upload_status.READY) +@celery.task +def complete_upload(request_id, quarantine_path, filename): + dst_dir = os.path.join( + current_app.config['UPLOAD_DIRECTORY'], + request_id + ) + fu.azure_upload(quarantine_path, os.path.join(dst_dir, filename)) + + def scan_file(filepath): """ Scans a file for viruses using McAfee Virus Scan. If an infected diff --git a/app/upload/views.py b/app/upload/views.py index 60c32e4d1..6ffdc3ea9 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -119,6 +119,7 @@ def post(request_id): fp.write(file_.stream.read()) # scan if last chunk written if os.path.getsize(filepath) == size: + file_size = os.path.getsize(filepath) scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) else: valid_file_type, file_type = is_valid_file_type(file_) @@ -127,6 +128,7 @@ def post(request_id): if valid_file_type: redis.set(key, upload_status.PROCESSING) file_.save(filepath) + file_size = os.path.getsize(filepath) scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) if not valid_file_type: @@ -142,7 +144,7 @@ def post(request_id): "files": [{ "name": filename, "original_name": file_.filename, - "size": os.path.getsize(filepath), + "size": file_size, }] } except Exception as e: diff --git a/config.py b/config.py index 2153a8d8b..8b94b4551 100644 --- a/config.py +++ b/config.py @@ -170,9 +170,12 @@ class Config: USE_SENTRY = os.environ.get('USE_SENTRY') == "True" # Azure Settings + USE_VOLUME_STORAGE = os.environ.get('USE_VOLUME_STORAGE') or False USE_AZURE_STORAGE = os.environ.get('USE_AZURE_STORAGE') or False AZURE_STORAGE_CONNECTION_STRING = os.environ.get('AZURE_STORAGE_CONNECTION_STRING') AZURE_STORAGE_CONTAINER = os.environ.get('AZURE_STORAGE_CONTAINER') + AZURE_STORAGE_ACCOUNT_NAME = os.environ.get('AZURE_STORAGE_ACCOUNT_NAME') + AZURE_STORAGE_ACCOUNT_KEY = os.environ.get('AZURE_STORAGE_ACCOUNT_KEY') @staticmethod def init_app(app): From cfbb8ad43fce248c80c361de88b7e91e36bf8ac7 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 12 Oct 2022 12:24:18 -0400 Subject: [PATCH 26/44] Changed serving files from SAS tokens instead of downloading --- app/lib/file_utils.py | 44 +++++++++++++++++++++++++------------------ app/response/views.py | 3 ++- app/upload/views.py | 3 ++- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 783be61ca..befde8cf9 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -14,7 +14,7 @@ from tempfile import TemporaryFile from functools import wraps from contextlib import contextmanager -from flask import current_app, send_from_directory +from flask import current_app, send_from_directory, redirect from app import sentry from azure.storage.blob import (generate_blob_sas, BlobSasPermissions, @@ -259,7 +259,8 @@ def send_file(directory, filename, **kwargs): # Download file from Azure if Azure storage is enabled if current_app.config['USE_AZURE_STORAGE']: - azure_download(path, os.path.join(directory, filename)) + blob_url = azure_generate_blob_url(os.path.join(directory, filename)) + return redirect(blob_url) return send_from_directory(*os.path.split(path), **kwargs) @@ -270,34 +271,44 @@ def create_azure_blob_client(blob_name): def azure_upload(source_path, blob_name): - connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') - blob_service_client = BlobServiceClient.from_connection_string(connection_string) - blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + blob_client = create_azure_blob_client(blob_name) with open(source_path, 'rb') as data: blob_client.upload_blob(data, overwrite=True) os.remove(source_path) def azure_download(source_path, blob_name): - connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') - blob_service_client = BlobServiceClient.from_connection_string(connection_string) - blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + blob_client = create_azure_blob_client(blob_name) with open(source_path, 'wb') as data: download_stream = blob_client.download_blob() data.write(download_stream.readall()) +def azure_generate_blob_url(blob_name): + # Generate SAS token + sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + account_key=current_app.config['AZURE_STORAGE_ACCOUNT_KEY'], + container_name=current_app.config['AZURE_STORAGE_CONTAINER'], + blob_name=blob_name, + permission=BlobSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=1)) + + # Generate blob URL + url = "https://{0}.blob.core.windows.net/{1}/{2}?{3}".format( + current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + current_app.config['AZURE_STORAGE_CONTAINER'], + blob_name, + sas_token) + return url + + def azure_exists(blob_name): - connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') - blob_service_client = BlobServiceClient.from_connection_string(connection_string) - blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + blob_client = create_azure_blob_client(blob_name) return blob_client.exists() def azure_delete(blob_name): - connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') - blob_service_client = BlobServiceClient.from_connection_string(connection_string) - blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], blob=blob_name) + blob_client = create_azure_blob_client(blob_name) blob_client.delete_blob() @@ -318,8 +329,5 @@ def azure_copy(current_blob_name, new_blob_name): sas_token) # Copy blob to new location - connection_string = os.getenv('AZURE_STORAGE_CONNECTION_STRING') - blob_service_client = BlobServiceClient.from_connection_string(connection_string) - blob_client = blob_service_client.get_blob_client(container=current_app.config['AZURE_STORAGE_CONTAINER'], - blob=new_blob_name) + blob_client = create_azure_blob_client(new_blob_name) blob_client.start_copy_from_url(url) \ No newline at end of file diff --git a/app/response/views.py b/app/response/views.py index 93894a6c1..c2046b709 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -762,7 +762,8 @@ def remove(resp): ).first() is not None): @after_this_request def remove(resp): - os.remove(serving_path) + if current_app.config['USE_SFTP']: + os.remove(serving_path) return resp return fu.send_file(*filepath_parts, as_attachment=True) diff --git a/app/upload/views.py b/app/upload/views.py index 6ffdc3ea9..56ee54b4b 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -130,7 +130,8 @@ def post(request_id): file_.save(filepath) file_size = os.path.getsize(filepath) scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) - + # Getting error with file_size not being set before reaching here + file_size = os.path.getsize(filepath) if not valid_file_type: response = { "files": [{ From 291a4ba7f6358720e581fd66d084000caab3453f Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 13 Oct 2022 16:42:18 -0400 Subject: [PATCH 27/44] Changed USE_SFTP checks to USE_VOLUME_STORAGE --- app/lib/file_utils.py | 2 +- app/request/utils.py | 2 +- app/response/utils.py | 4 ++-- app/response/views.py | 4 ++-- app/upload/utils.py | 2 +- app/upload/views.py | 2 +- config.py | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index befde8cf9..558974c71 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -254,7 +254,7 @@ def os_get_hash(path): def send_file(directory, filename, **kwargs): path = _get_file_serving_path(directory, filename) # Move file to data directory if volume storage enabled - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: shutil.copy(os.path.join(directory, filename), path) # Download file from Azure if Azure storage is enabled diff --git a/app/request/utils.py b/app/request/utils.py index 704974f1a..77fdf8fe1 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -373,7 +373,7 @@ def _move_validated_upload(request_id, tmp_path): redis_set_file_metadata(request_id, tmp_path) # Move file to data directory if volume storage is enabled - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: fu.move(tmp_path, valid_path) # Upload file to Azure if Azure storage is enabled diff --git a/app/response/utils.py b/app/response/utils.py index 3c622b94d..0fbf832a3 100644 --- a/app/response/utils.py +++ b/app/response/utils.py @@ -2836,7 +2836,7 @@ def replace_old_file(self, updated_filepath, new_filename): current_app.config['UPLOAD_DIRECTORY'], self.response.request_id ) - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: fu.remove( os.path.join( upload_path, @@ -2950,7 +2950,7 @@ def move_deleted_file(self): DELETED_FILE_DIRNAME, str(self.response.id) ) - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: if not fu.exists(dir_deleted): fu.makedirs(dir_deleted) fu.rename( diff --git a/app/response/views.py b/app/response/views.py index c2046b709..1693819a4 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -723,7 +723,7 @@ def get_response_content(response_id): response_.name ) token = flask_request.args.get('token') - if current_app.config['USE_SFTP'] and not fu.exists(filepath): + if current_app.config['USE_VOLUME_STORAGE'] and not fu.exists(filepath): return abort(403) if response_.is_public: @@ -762,7 +762,7 @@ def remove(resp): ).first() is not None): @after_this_request def remove(resp): - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: os.remove(serving_path) return resp diff --git a/app/upload/utils.py b/app/upload/utils.py index 392638222..b5e55d57f 100644 --- a/app/upload/utils.py +++ b/app/upload/utils.py @@ -168,7 +168,7 @@ def scan_and_complete_upload(request_id, filepath, is_update=False, response_id= ) # store file metadata in redis redis_set_file_metadata(response_id or request_id, filepath, is_update) - if current_app.config['USE_SFTP']: + if current_app.config['USE_VOLUME_STORAGE']: if not fu.exists(dst_dir): try: fu.makedirs(dst_dir) diff --git a/app/upload/views.py b/app/upload/views.py index 56ee54b4b..9ec74de35 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -236,7 +236,7 @@ def delete(r_id_type, r_id, filecode): os.remove(filepath) found = True else: - if current_app.config['USE_SFTP'] and fu.exists(filepath): + if current_app.config['USE_VOLUME_STORAGE'] and fu.exists(filepath): fu.remove(filepath) found = True elif current_app.config['USE_AZURE_STORAGE'] and fu.azure_exists(filepath): diff --git a/config.py b/config.py index 8b94b4551..dbc269acd 100644 --- a/config.py +++ b/config.py @@ -170,8 +170,8 @@ class Config: USE_SENTRY = os.environ.get('USE_SENTRY') == "True" # Azure Settings - USE_VOLUME_STORAGE = os.environ.get('USE_VOLUME_STORAGE') or False - USE_AZURE_STORAGE = os.environ.get('USE_AZURE_STORAGE') or False + USE_VOLUME_STORAGE = os.environ.get('USE_VOLUME_STORAGE') == "True" + USE_AZURE_STORAGE = os.environ.get('USE_AZURE_STORAGE') == "True" AZURE_STORAGE_CONNECTION_STRING = os.environ.get('AZURE_STORAGE_CONNECTION_STRING') AZURE_STORAGE_CONTAINER = os.environ.get('AZURE_STORAGE_CONTAINER') AZURE_STORAGE_ACCOUNT_NAME = os.environ.get('AZURE_STORAGE_ACCOUNT_NAME') From 68b0a9e4f0e88df5da83884938c20e57685e2ab7 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Fri, 14 Oct 2022 15:53:08 -0400 Subject: [PATCH 28/44] Fixed bug with uploads not working on volume storage --- app/response/utils.py | 24 +++++++++--------------- app/response/views.py | 2 +- app/upload/utils.py | 42 ++++++++++++++++-------------------------- app/upload/views.py | 12 ++++-------- 4 files changed, 30 insertions(+), 50 deletions(-) diff --git a/app/response/utils.py b/app/response/utils.py index 0fbf832a3..f5a99b4d7 100644 --- a/app/response/utils.py +++ b/app/response/utils.py @@ -2820,14 +2820,14 @@ def set_edited_data(self): hash_) if self.update: redis_delete_file_metadata(self.response.id, filepath, is_update=True) - self.replace_old_file(filepath, new_filename) + self.replace_old_file(new_filename) else: self.errors.append( "File '{}' not found.".format(new_filename)) if self.update: self.handle_response_token(bool(new_filename)) - def replace_old_file(self, updated_filepath, new_filename): + def replace_old_file(self, new_filename): """ Move the new file out of the 'updated' directory and delete the file it is replacing. @@ -2836,6 +2836,11 @@ def replace_old_file(self, updated_filepath, new_filename): current_app.config['UPLOAD_DIRECTORY'], self.response.request_id ) + quarantine_path = os.path.join( + current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], + self.response.request_id, + new_filename + ) if current_app.config['USE_VOLUME_STORAGE']: fu.remove( os.path.join( @@ -2843,13 +2848,7 @@ def replace_old_file(self, updated_filepath, new_filename): self.response.name ) ) - fu.rename( - updated_filepath, - os.path.join( - upload_path, - os.path.basename(updated_filepath) - ) - ) + complete_upload.delay(self.response.request_id, quarantine_path, new_filename) if current_app.config['USE_AZURE_STORAGE']: fu.azure_delete( os.path.join( @@ -2857,12 +2856,7 @@ def replace_old_file(self, updated_filepath, new_filename): self.response.name ) ) - quarantine_path = os.path.join( - current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], - self.response.request_id, - new_filename - ) - complete_upload(self.response.request_id, quarantine_path, new_filename) + complete_upload.delay(self.response.request_id, quarantine_path, new_filename) def handle_response_token(self, file_changed): diff --git a/app/response/views.py b/app/response/views.py index 1693819a4..8548afa41 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -152,7 +152,7 @@ def response_file(request_id): current_app.config['UPLOAD_QUARANTINE_DIRECTORY'], request_id, file_data) - complete_upload(current_request.id, quarantine_path, file_data) + complete_upload.delay(current_request.id, quarantine_path, file_data) response_obj = add_file(current_request.id, file_data, files[file_data]['title'], diff --git a/app/upload/utils.py b/app/upload/utils.py index b5e55d57f..86519b709 100644 --- a/app/upload/utils.py +++ b/app/upload/utils.py @@ -128,7 +128,7 @@ def __init__(self, filename): @celery.task -def scan_and_complete_upload(request_id, filepath, is_update=False, response_id=None): +def scan_upload(request_id, filepath, is_update=False, response_id=None): """ Scans an uploaded file (see scan_file) and moves it to the data directory if it is clean. If is_update is set, @@ -156,31 +156,8 @@ def scan_and_complete_upload(request_id, filepath, is_update=False, response_id= sentry.captureException() redis.delete(key) else: - # complete upload - dst_dir = os.path.join( - current_app.config['UPLOAD_DIRECTORY'], - request_id - ) - if is_update: - dst_dir = os.path.join( - dst_dir, - UPDATED_FILE_DIRNAME - ) # store file metadata in redis redis_set_file_metadata(response_id or request_id, filepath, is_update) - if current_app.config['USE_VOLUME_STORAGE']: - if not fu.exists(dst_dir): - try: - fu.makedirs(dst_dir) - except OSError as e: - sentry.captureException() - # in the time between the call to fu.exists - # and fu.makedirs, the directory was created - current_app.logger.error("OS Error: {}".format(e.args)) - fu.move( - filepath, - os.path.join(dst_dir, filename) - ) redis.set(key, upload_status.READY) @@ -190,8 +167,21 @@ def complete_upload(request_id, quarantine_path, filename): current_app.config['UPLOAD_DIRECTORY'], request_id ) - fu.azure_upload(quarantine_path, os.path.join(dst_dir, filename)) - + if current_app.config['USE_VOLUME_STORAGE']: + if not fu.exists(dst_dir): + try: + fu.makedirs(dst_dir) + except OSError as e: + sentry.captureException() + # in the time between the call to fu.exists + # and fu.makedirs, the directory was created + current_app.logger.error("OS Error: {}".format(e.args)) + fu.move( + quarantine_path, + os.path.join(dst_dir, filename) + ) + if current_app.config['USE_AZURE_STORAGE']: + fu.azure_upload(quarantine_path, os.path.join(dst_dir, filename)) def scan_file(filepath): """ diff --git a/app/upload/views.py b/app/upload/views.py index 9ec74de35..d0a9330f6 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -38,7 +38,7 @@ from app.upload.utils import ( parse_content_range, is_valid_file_type, - scan_and_complete_upload, + scan_upload, get_upload_key, upload_exists, ) @@ -119,8 +119,7 @@ def post(request_id): fp.write(file_.stream.read()) # scan if last chunk written if os.path.getsize(filepath) == size: - file_size = os.path.getsize(filepath) - scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) + scan_upload.delay(request_id, filepath, is_update, response_id) else: valid_file_type, file_type = is_valid_file_type(file_) if current_user.is_agency_active(agency_ein): @@ -128,10 +127,7 @@ def post(request_id): if valid_file_type: redis.set(key, upload_status.PROCESSING) file_.save(filepath) - file_size = os.path.getsize(filepath) - scan_and_complete_upload.delay(request_id, filepath, is_update, response_id) - # Getting error with file_size not being set before reaching here - file_size = os.path.getsize(filepath) + scan_upload.delay(request_id, filepath, is_update, response_id) if not valid_file_type: response = { "files": [{ @@ -145,7 +141,7 @@ def post(request_id): "files": [{ "name": filename, "original_name": file_.filename, - "size": file_size, + "size": os.path.getsize(filepath), }] } except Exception as e: From 9e3bea95cfdafb0a7b05bef53d6015e269123a90 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Mon, 17 Oct 2022 16:22:07 -0400 Subject: [PATCH 29/44] Cleanup --- app/lib/file_utils.py | 29 +++-------------------------- app/request/utils.py | 3 +-- app/response/utils.py | 7 +++---- app/upload/utils.py | 13 +++++++++---- app/upload/views.py | 1 + 5 files changed, 17 insertions(+), 36 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 558974c71..197a00e71 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -256,9 +256,8 @@ def send_file(directory, filename, **kwargs): # Move file to data directory if volume storage enabled if current_app.config['USE_VOLUME_STORAGE']: shutil.copy(os.path.join(directory, filename), path) - - # Download file from Azure if Azure storage is enabled - if current_app.config['USE_AZURE_STORAGE']: + # Serve file from Azure if Azure storage is enabled + elif current_app.config['USE_AZURE_STORAGE']: blob_url = azure_generate_blob_url(os.path.join(directory, filename)) return redirect(blob_url) return send_from_directory(*os.path.split(path), **kwargs) @@ -277,13 +276,6 @@ def azure_upload(source_path, blob_name): os.remove(source_path) -def azure_download(source_path, blob_name): - blob_client = create_azure_blob_client(blob_name) - with open(source_path, 'wb') as data: - download_stream = blob_client.download_blob() - data.write(download_stream.readall()) - - def azure_generate_blob_url(blob_name): # Generate SAS token sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], @@ -313,21 +305,6 @@ def azure_delete(blob_name): def azure_copy(current_blob_name, new_blob_name): - # Generate SAS token - sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], - account_key=current_app.config['AZURE_STORAGE_ACCOUNT_KEY'], - container_name=current_app.config['AZURE_STORAGE_CONTAINER'], - blob_name=current_blob_name, - permission=BlobSasPermissions(read=True), - expiry=datetime.utcnow() + timedelta(hours=1)) - - # Generate blob URL - url = "https://{0}.blob.core.windows.net/{1}/{2}?{3}".format( - current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], - current_app.config['AZURE_STORAGE_CONTAINER'], - current_blob_name, - sas_token) - - # Copy blob to new location blob_client = create_azure_blob_client(new_blob_name) + url = generate_blob_sas(current_blob_name) blob_client.start_copy_from_url(url) \ No newline at end of file diff --git a/app/request/utils.py b/app/request/utils.py index 77fdf8fe1..656322e41 100644 --- a/app/request/utils.py +++ b/app/request/utils.py @@ -375,9 +375,8 @@ def _move_validated_upload(request_id, tmp_path): # Move file to data directory if volume storage is enabled if current_app.config['USE_VOLUME_STORAGE']: fu.move(tmp_path, valid_path) - # Upload file to Azure if Azure storage is enabled - if current_app.config['USE_AZURE_STORAGE']: + elif current_app.config['USE_AZURE_STORAGE']: fu.azure_upload(tmp_path, valid_path) upload_redis.set( diff --git a/app/response/utils.py b/app/response/utils.py index f5a99b4d7..c3a5e741a 100644 --- a/app/response/utils.py +++ b/app/response/utils.py @@ -2829,8 +2829,7 @@ def set_edited_data(self): def replace_old_file(self, new_filename): """ - Move the new file out of the 'updated' directory - and delete the file it is replacing. + Delete old file and move new file to upload directory from quarantine directory """ upload_path = os.path.join( current_app.config['UPLOAD_DIRECTORY'], @@ -2849,7 +2848,7 @@ def replace_old_file(self, new_filename): ) ) complete_upload.delay(self.response.request_id, quarantine_path, new_filename) - if current_app.config['USE_AZURE_STORAGE']: + elif current_app.config['USE_AZURE_STORAGE']: fu.azure_delete( os.path.join( upload_path, @@ -2957,7 +2956,7 @@ def move_deleted_file(self): self.response.name ) ) - if current_app.config['USE_AZURE_STORAGE']: + elif current_app.config['USE_AZURE_STORAGE']: deleted_filename = os.path.join( dir_deleted, self.response.name diff --git a/app/upload/utils.py b/app/upload/utils.py index 86519b709..8865475ea 100644 --- a/app/upload/utils.py +++ b/app/upload/utils.py @@ -130,9 +130,7 @@ def __init__(self, filename): @celery.task def scan_upload(request_id, filepath, is_update=False, response_id=None): """ - Scans an uploaded file (see scan_file) and moves - it to the data directory if it is clean. If is_update is set, - the file will also be placed under the 'updated' directory. + Scans an uploaded file (see scan_file). Updates redis accordingly. :param request_id: id of request associated with the upload @@ -163,6 +161,13 @@ def scan_upload(request_id, filepath, is_update=False, response_id=None): @celery.task def complete_upload(request_id, quarantine_path, filename): + """ + Complete file upload to volume storage or Azure storage. + + :param request_id: id of request associated with the upload + :param quarantine_path: path to quarantined file + :param filename: name of file being uploaded + """ dst_dir = os.path.join( current_app.config['UPLOAD_DIRECTORY'], request_id @@ -180,7 +185,7 @@ def complete_upload(request_id, quarantine_path, filename): quarantine_path, os.path.join(dst_dir, filename) ) - if current_app.config['USE_AZURE_STORAGE']: + elif current_app.config['USE_AZURE_STORAGE']: fu.azure_upload(quarantine_path, os.path.join(dst_dir, filename)) def scan_file(filepath): diff --git a/app/upload/views.py b/app/upload/views.py index d0a9330f6..c9c62e064 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -232,6 +232,7 @@ def delete(r_id_type, r_id, filecode): os.remove(filepath) found = True else: + # Check storage solution and delete file accordingly if current_app.config['USE_VOLUME_STORAGE'] and fu.exists(filepath): fu.remove(filepath) found = True From 0ee7ebd46331d102c5128edffe368c74aaf0c1ce Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Mon, 17 Oct 2022 16:29:37 -0400 Subject: [PATCH 30/44] Updated .env.example for Azure storage --- .env.example | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.env.example b/.env.example index 5ba8f5834..9cfc1be2b 100644 --- a/.env.example +++ b/.env.example @@ -115,3 +115,11 @@ RECAPTCHA_SECRET_KEY_V3= # Sentry SENTRY_DSN= USE_SENTRY=False + +# Azure Storage Settings +USE_AZURE_STORAGE=True +USE_VOLUME_STORAGE=False +AZURE_STORAGE_CONNECTION_STRING= +AZURE_STORAGE_CONTAINER= +AZURE_STORAGE_ACCOUNT_NAME= +AZURE_STORAGE_ACCOUNT_KEY= \ No newline at end of file From 88af1e8ff00c55536e321bfe8d28701551157123 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Mon, 17 Oct 2022 16:39:02 -0400 Subject: [PATCH 31/44] Fixed response token check for anon/public users --- app/lib/file_utils.py | 2 +- app/response/views.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 197a00e71..65061d5be 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -306,5 +306,5 @@ def azure_delete(blob_name): def azure_copy(current_blob_name, new_blob_name): blob_client = create_azure_blob_client(new_blob_name) - url = generate_blob_sas(current_blob_name) + url = azure_generate_blob_url(current_blob_name) blob_client.start_copy_from_url(url) \ No newline at end of file diff --git a/app/response/views.py b/app/response/views.py index 8548afa41..acca377a0 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -730,7 +730,8 @@ def get_response_content(response_id): # then we just serve the file, anyone can view it @after_this_request def remove(resp): - os.remove(serving_path) + if current_app.config['USE_VOLUME_STORAGE']: + os.remove(serving_path) return resp return fu.send_file(*filepath_parts, as_attachment=True) @@ -743,7 +744,8 @@ def remove(resp): if response_.privacy != PRIVATE: @after_this_request def remove(resp): - os.remove(serving_path) + if current_app.config['USE_VOLUME_STORAGE']: + os.remove(serving_path) return resp return fu.send_file(*filepath_parts, as_attachment=True) From b974005ea5728ee9de63a4340b4a60e84b0d3644 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 10 Nov 2022 12:59:45 -0500 Subject: [PATCH 32/44] Updated deprecated celery setting names --- app/celery_config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/celery_config.py b/app/celery_config.py index 34aaaef4d..59e36d7c0 100644 --- a/app/celery_config.py +++ b/app/celery_config.py @@ -8,14 +8,14 @@ dotenv_path = os.path.join(basedir, '.env') load_dotenv(dotenv_path) -CELERY_IMPORTS = ['app.jobs'] -CELERY_TASK_RESULT_EXPIRES = 30 -CELERY_TIMEZONE = 'EST' +imports = ['app.jobs'] +result_expires = 30 +timezone = 'EST' CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL = os.environ.get('CELERY_CLEAR_EXPIRED_SESSION_IDS_INTERVAL', '*/1') -CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] +accept_content = ['pickle', 'json', 'msgpack', 'yaml'] -CELERYBEAT_SCHEDULE = { +beat_schedule = { # Every weekday at 7AM EST 'update_request_statuses': { 'task': 'app.jobs.update_request_statuses', From fa02d8efa026136e8b0297ec2b103b63e5092690 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 10 Nov 2022 15:22:35 -0500 Subject: [PATCH 33/44] Added SFTP check for file deletion --- app/lib/file_utils.py | 32 ++++++++++++++++---------------- app/upload/views.py | 3 ++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/lib/file_utils.py b/app/lib/file_utils.py index 65061d5be..021fb816e 100644 --- a/app/lib/file_utils.py +++ b/app/lib/file_utils.py @@ -277,21 +277,21 @@ def azure_upload(source_path, blob_name): def azure_generate_blob_url(blob_name): - # Generate SAS token - sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], - account_key=current_app.config['AZURE_STORAGE_ACCOUNT_KEY'], - container_name=current_app.config['AZURE_STORAGE_CONTAINER'], - blob_name=blob_name, - permission=BlobSasPermissions(read=True), - expiry=datetime.utcnow() + timedelta(hours=1)) - - # Generate blob URL - url = "https://{0}.blob.core.windows.net/{1}/{2}?{3}".format( - current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], - current_app.config['AZURE_STORAGE_CONTAINER'], - blob_name, - sas_token) - return url + # Generate SAS token + sas_token = generate_blob_sas(account_name=current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + account_key=current_app.config['AZURE_STORAGE_ACCOUNT_KEY'], + container_name=current_app.config['AZURE_STORAGE_CONTAINER'], + blob_name=blob_name, + permission=BlobSasPermissions(read=True), + expiry=datetime.utcnow() + timedelta(hours=1)) + + # Generate blob URL + url = "https://{0}.blob.core.windows.net/{1}/{2}?{3}".format( + current_app.config['AZURE_STORAGE_ACCOUNT_NAME'], + current_app.config['AZURE_STORAGE_CONTAINER'], + blob_name, + sas_token) + return url def azure_exists(blob_name): @@ -307,4 +307,4 @@ def azure_delete(blob_name): def azure_copy(current_blob_name, new_blob_name): blob_client = create_azure_blob_client(new_blob_name) url = azure_generate_blob_url(current_blob_name) - blob_client.start_copy_from_url(url) \ No newline at end of file + blob_client.start_copy_from_url(url) diff --git a/app/upload/views.py b/app/upload/views.py index c9c62e064..bfb7dd3e3 100644 --- a/app/upload/views.py +++ b/app/upload/views.py @@ -233,7 +233,8 @@ def delete(r_id_type, r_id, filecode): found = True else: # Check storage solution and delete file accordingly - if current_app.config['USE_VOLUME_STORAGE'] and fu.exists(filepath): + if (current_app.config['USE_VOLUME_STORAGE'] or current_app.config['USE_SFTP'])\ + and fu.exists(filepath): fu.remove(filepath) found = True elif current_app.config['USE_AZURE_STORAGE'] and fu.azure_exists(filepath): From f8271a1dcb4fc1771862f314ee7fce6da1a46b09 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Tue, 22 Nov 2022 17:01:00 -0500 Subject: [PATCH 34/44] Updated send_file parameter name --- app/report/views.py | 2 +- app/response/views.py | 3 +-- app/search/views.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/report/views.py b/app/report/views.py index 5c5861ce9..f457b1a81 100644 --- a/app/report/views.py +++ b/app/report/views.py @@ -230,7 +230,7 @@ def open_data_report(): date_to_string = date_to.strftime('%Y%m%d') return send_file( BytesIO(open_data_report_spreadsheet), - attachment_filename='open_data_compliance_report_{}_{}.xls'.format(date_from_string, date_to_string), + download_name='open_data_compliance_report_{}_{}.xls'.format(date_from_string, date_to_string), as_attachment=True ) else: diff --git a/app/response/views.py b/app/response/views.py index ec7614040..4cfd73ba1 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -837,8 +837,7 @@ def response_get_envelope(request_id, response_id): io.BytesIO(f), mimetype='application/pdf', as_attachment=True, - attachment_filename= - '{request_id}_envelope.pdf'.format(request_id=request_id) + download_name='{request_id}_envelope.pdf'.format(request_id=request_id) ) diff --git a/app/search/views.py b/app/search/views.py index 6d64693ca..4a7cb7a89 100644 --- a/app/search/views.py +++ b/app/search/views.py @@ -276,7 +276,7 @@ def requests_doc(doc_type): timestamp = utc_to_local(dt, tz_name) if tz_name is not None else dt return send_file( BytesIO(buffer.getvalue().encode("UTF-8")), # convert to bytes - attachment_filename="FOIL_requests_results_{}.csv".format( + download_name="FOIL_requests_results_{}.csv".format( timestamp.strftime("%m_%d_%Y_at_%I_%M_%p") ), as_attachment=True, From f54420afa91dfabb9b50cc154943a4d26e20d390 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 30 Nov 2022 15:12:03 -0500 Subject: [PATCH 35/44] Added agency name and acronym to es_update --- app/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models.py b/app/models.py index 2a6ac89f8..5c99e4007 100644 --- a/app/models.py +++ b/app/models.py @@ -1010,6 +1010,8 @@ def es_update(self): "public_title": "Private" if self.privacy["title"] else self.title, + "agency_name": self.agency.name, + "agency_acronym": self.agency.acronym } }, # refresh='wait_for' From 26b6cb423fd014e88ccdf5c664a30898c5875f23 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 15 Dec 2022 10:43:27 -0500 Subject: [PATCH 36/44] Removed unused code and added custom form name fix --- app/request/views.py | 3 +-- app/response/views.py | 1 - app/static/js/request/main.js | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/request/views.py b/app/request/views.py index 272b8e3a7..8dc785aa0 100644 --- a/app/request/views.py +++ b/app/request/views.py @@ -114,7 +114,6 @@ def new(): if flask_request.form["tz-name"] else current_app.config["APP_TIMEZONE"] ) - print(form.request_agency.data) if current_user.is_public: request_id = create_request( escape(form.request_title.data), @@ -256,7 +255,7 @@ def view(request_id): assigned_users = [] if current_user.is_agency: for agency_user in current_request.agency.active_users: - if not agency_user in current_request.agency.administrators and ( + if agency_user not in current_request.agency_administrators and ( agency_user != current_user ) and not agency_user.is_agency_read_only(current_request.agency_ein): # populate list of assigned users that can be removed from a request diff --git a/app/response/views.py b/app/response/views.py index 4cfd73ba1..7870f0d7a 100644 --- a/app/response/views.py +++ b/app/response/views.py @@ -711,7 +711,6 @@ def get_response_content(response_id): 400 error if response/file not found """ response_ = Responses.query.filter_by(id=response_id, deleted=False).one() - request = Requests.query.filter_by(id=response_.request_id).one() if response_ is not None and response_.type == FILE: upload_path = os.path.join( diff --git a/app/static/js/request/main.js b/app/static/js/request/main.js index d2dbc6842..e96439268 100644 --- a/app/static/js/request/main.js +++ b/app/static/js/request/main.js @@ -739,7 +739,7 @@ function processCustomRequestFormData() { var formKey = "form_"; var fieldKey = "field_"; var formId = $("#request-type-" + target + " option:selected").val(); - var formName = originalFormNames[formId]; + var formName = $("#request-type-" + target + " option:selected").text(); var previousRadioId = ""; var completedFields = 0; From 040f1ee8cd9317922a6bbf542d1d18fa3eb02de6 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 21 Dec 2022 10:33:22 -0500 Subject: [PATCH 37/44] Added option to toggle MFA --- Pipfile | 2 ++ app/__init__.py | 16 +++++++++------- app/main/views.py | 2 +- app/report/utils.py | 10 +++++----- app/templates/auth/manage_account.html | 8 +++++--- config.py | 1 + 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Pipfile b/Pipfile index 95171d00b..46ddaef8a 100644 --- a/Pipfile +++ b/Pipfile @@ -116,6 +116,8 @@ python-magic = "*" gunicorn = "*" azure-core = "*" azure-storage-blob = "*" +xlwt = "*" +xlrd = "*" [requires] python_version = "3.10.2" diff --git a/app/__init__.py b/app/__init__.py index 029f65ecf..6e2d06ecd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -173,11 +173,12 @@ def check_maintenance_mode(): if not flask_request.cookies.get('authorized_maintainer', None): return abort(503) - @app.before_request - def check_valid_login(): - if current_user.is_authenticated: - if not session.get('mfa_verified', False) and flask_request.endpoint not in ['mfa.register', 'mfa.verify', 'static', 'auth.logout']: - return redirect(url_for('mfa.verify')) + if app.config['USE_MFA']: + @app.before_request + def check_valid_login(): + if current_user.is_authenticated: + if not session.get('mfa_verified', False) and flask_request.endpoint not in ['mfa.register', 'mfa.verify', 'static', 'auth.logout']: + return redirect(url_for('mfa.verify')) @app.context_processor def add_session_config(): @@ -240,7 +241,8 @@ def add_debug(): from .permissions import permissions app.register_blueprint(permissions, url_prefix="/permissions/api/v1.0") - from .mfa import mfa - app.register_blueprint(mfa, url_prefix="/mfa") + if app.config['USE_MFA']: + from .mfa import mfa + app.register_blueprint(mfa, url_prefix="/mfa") return app diff --git a/app/main/views.py b/app/main/views.py index 0a4a7a5aa..d9ec2e2b7 100644 --- a/app/main/views.py +++ b/app/main/views.py @@ -28,7 +28,7 @@ def index(): fresh_login = request.args.get('fresh_login', False) verify_mfa = request.args.get('verify_mfa', False) if current_user.is_authenticated: - if verify_mfa: + if verify_mfa and current_app.config['USE_MFA']: if current_user.has_mfa: return redirect(url_for('mfa.verify')) else: diff --git a/app/report/utils.py b/app/report/utils.py index eee24911b..81dd004ca 100644 --- a/app/report/utils.py +++ b/app/report/utils.py @@ -217,10 +217,8 @@ def generate_request_closing_user_report(agency_ein: str, date_from: str, date_t func.to_char(Requests.due_date, 'MM/DD/YYYY'), func.to_char(Events.timestamp, 'MM/DD/YYYY HH:MI:SS.MS'), Users.fullname, - ).distinct().join( - Events, - Users, - ).filter( + Requests.description + ).distinct().join(Events, Events.request_id == Requests.id).join(Users, Users.guid == Events.user_guid).filter( Events.timestamp.between(date_from_utc, date_to_utc), Requests.agency_ein == agency_ein, Events.type.in_((REQ_CLOSED, REQ_DENIED)), @@ -231,12 +229,14 @@ def generate_request_closing_user_report(agency_ein: str, date_from: str, date_t person_month_list = [list(r) for r in person_month] for person_month_item in person_month_list: person_month_item[4] = person_month_item[4].split(' ', 1)[0] + person_month_item[6] = Markup(person_month_item[6]).unescape() person_month_headers = ('Request ID', 'Status', 'Date Created', 'Due Date', 'Timestamp', - 'Closed By') + 'Closed By', + 'Request Description') person_month_dataset = tablib.Dataset(*person_month_list, headers=person_month_headers, title='month closed by person Raw Data') diff --git a/app/templates/auth/manage_account.html b/app/templates/auth/manage_account.html index af06784eb..8ea085a0b 100644 --- a/app/templates/auth/manage_account.html +++ b/app/templates/auth/manage_account.html @@ -25,9 +25,11 @@

Manage OpenRecords Account

-
- Manage MFA Devices -
+ {% if config.USE_MFA %} +
+ Manage MFA Devices +
+ {% endif %} {{ form.csrf_token }} diff --git a/config.py b/config.py index dbc269acd..770c7d7c8 100644 --- a/config.py +++ b/config.py @@ -63,6 +63,7 @@ class Config: SESSION_TYPE = os.environ.get('SESSION_TYPE', 'redis') USE_SAML = os.environ.get('USE_SAML') == "True" MFA_ENCRYPT_FILE = os.environ.get('MFA_ENCRYPT_FILE') + USE_MFA = os.environ.get('USE_MFA') == "True" AUTH_TYPE = 'None' From 99169d7253f1e50c22722f639d0238dec5ab4621 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Fri, 20 Jan 2023 14:41:45 -0500 Subject: [PATCH 38/44] Add minor code review changes --- .env.example | 1 + Pipfile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 9cfc1be2b..5aab1612b 100644 --- a/.env.example +++ b/.env.example @@ -75,6 +75,7 @@ SFTP_UPLOAD_DIRECTORY= PERMANENT_SESSION_LIFETIME=20 # MFA +USE_MFA= MFA_ENCRYPT_FILE= # SAML diff --git a/Pipfile b/Pipfile index 46ddaef8a..5e1fcbb1f 100644 --- a/Pipfile +++ b/Pipfile @@ -116,6 +116,8 @@ python-magic = "*" gunicorn = "*" azure-core = "*" azure-storage-blob = "*" + +# Required for additional formats in tablib xlwt = "*" xlrd = "*" From e7a3395f87c314ea5be6d41be6090259e80e3eca Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Fri, 27 Jan 2023 11:16:19 -0500 Subject: [PATCH 39/44] Fixed open data compliance report missing referrer header error --- app/templates/report/reports.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/report/reports.html b/app/templates/report/reports.html index c180cd867..353830006 100644 --- a/app/templates/report/reports.html +++ b/app/templates/report/reports.html @@ -80,7 +80,7 @@

Monthly FOIL Metrics



Open Data Compliance Report

-
+ {{ open_data_report_form.csrf_token }}
{{ open_data_report_form.date_from.label }} From f19865d0e9f55261c1d9ab0b57cd6173eeaef4b7 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Fri, 27 Jan 2023 11:16:51 -0500 Subject: [PATCH 40/44] Fixed open data compliance report missing referrer header error --- app/templates/report/reports.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/report/reports.html b/app/templates/report/reports.html index c180cd867..353830006 100644 --- a/app/templates/report/reports.html +++ b/app/templates/report/reports.html @@ -80,7 +80,7 @@

Monthly FOIL Metrics



Open Data Compliance Report

- + {{ open_data_report_form.csrf_token }}
{{ open_data_report_form.date_from.label }} From 95b49294dd54192a1d22cb1f4eb4925756c4ccd0 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 15 Feb 2023 15:56:06 -0500 Subject: [PATCH 41/44] Update _generate_signature method to use HMAC-SHA256 --- app/auth/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/auth/utils.py b/app/auth/utils.py index 00a35ff22..327440d87 100644 --- a/app/auth/utils.py +++ b/app/auth/utils.py @@ -16,7 +16,7 @@ abort, current_app, flash, redirect, request, session, url_for ) from flask_login import current_user, login_user, logout_user -from hashlib import sha1 +from hashlib import sha256 from ldap3 import Connection, Server, Tls from requests.exceptions import SSLError @@ -684,10 +684,10 @@ def _generate_signature(password, string): """ signature = None try: - hmac_sha1 = hmac.new(key=password.encode(), - msg=string.encode(), - digestmod=sha1) - signature = hmac_sha1.hexdigest() + hmac_sha256 = hmac.new(key=password.encode(), + msg=string.encode(), + digestmod=sha256) + signature = hmac_sha256.hexdigest() except Exception as e: sentry.captureException() current_app.logger.error("Failed to generate NYC ID.Web Services " From a9daa6f5590d12dc46d1b0596ecc2639f3041bf3 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 22 Feb 2023 13:16:41 -0500 Subject: [PATCH 42/44] Updated google translate styles --- app/static/styles/base.css | 88 ++++++++++++------- app/templates/base.html | 25 +++--- app/templates/base.js.html | 10 +++ .../nginx_conf/sites/openrecords_v2_0.conf | 5 +- 4 files changed, 78 insertions(+), 50 deletions(-) diff --git a/app/static/styles/base.css b/app/static/styles/base.css index 8388ff4ca..c748bd6cc 100644 --- a/app/static/styles/base.css +++ b/app/static/styles/base.css @@ -43,7 +43,7 @@ } @media (max-width: 767px) { - #google_translate_element { + .translate-btn { display: none; } @@ -98,39 +98,6 @@ footer .form-search .input-search { font-size: 16px; } -#google_translate_element { - padding-top: 4rem; - padding-left: 4rem; -} - -.goog-te-gadget-icon { - display: none; -} - -.goog-te-menu-value { - display: none; -} - -.goog-te-menu-value2 { - text-decoration: none; - color: #0000cc; - white-space: nowrap; - margin-left: 4px; - margin-right: 4px; - border: none; -} - -.goog-te-gadget-simple { - background-color: #fff; - border: none; - font-size: 10pt; - display: inline-block; - padding-top: 1px; - padding-bottom: 2px; - cursor: pointer; - zoom: 1; -} - .row { width: 100%; margin-left: 0; @@ -227,4 +194,57 @@ input[type="radio"], input[type="checkbox"] { .parsley-errors-list.filled { font-size: 14px; color: #df0000; +} + +.translate-btn { + color: currentColor; + font-weight: inherit; + border-color: transparent; + background-color: transparent; + padding-top: 4rem; + padding-left: 4rem; + cursor: pointer; +} + +#global-language { + width: 100%; + padding-right: 1rem; + padding-left: 1rem; + margin-right: auto; + margin-left: auto; + border-top: 1px solid #dee2e6 !important; + border-bottom: 1px solid #dee2e6 !important; +} + +.py-2 { + padding-top: 2rem !important; + padding-bottom: 2rem !important; +} + +.narrow { + margin-left: auto; + margin-right: auto; +} + +#google_translate_element { + text-align: center; +} + +#google_translate_element .goog-logo-link { + margin-left: auto; + margin-right: auto; +} + +#google_translate_element select { + width: 25%; + height: 5rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 2rem; +} + +html[dir="rtl"] select:not(.notranslate):not(.goog-te-combo) { + background-position-x: 0.75rem !important; + padding: 0.375rem 0.75rem 0.375rem 1.75rem; } \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 496ff7a31..c4caa048d 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -29,7 +29,6 @@ function googleTranslateElementInit() { new google.translate.TranslateElement({ pageLanguage: 'en', - layout: google.translate.TranslateElement.InlineLayout.SIMPLE }, 'google_translate_element'); } @@ -44,20 +43,20 @@
-
- -
+
- +
+
+
+
+
{% endblock %}
diff --git a/app/templates/base.js.html b/app/templates/base.js.html index efee4e63d..c79f1a655 100644 --- a/app/templates/base.js.html +++ b/app/templates/base.js.html @@ -41,5 +41,15 @@ {% endif %} {% endif %} + // Toggle for google translate + $('#global-language').on('show.bs.collapse', function () { + $('#global-search').collapse('hide'); + $('#nav-primary').collapse('hide'); + }); + $('#global-language').on('shown.bs.collapse', function () { + $('.goog-te-combo').focus(); + }).on('show.bs.collapse', function () { + $('.goog-te-combo').blur(); + }); \ No newline at end of file diff --git a/build_scripts/default/nginx_conf/sites/openrecords_v2_0.conf b/build_scripts/default/nginx_conf/sites/openrecords_v2_0.conf index f6b498a8a..72213de5d 100644 --- a/build_scripts/default/nginx_conf/sites/openrecords_v2_0.conf +++ b/build_scripts/default/nginx_conf/sites/openrecords_v2_0.conf @@ -46,7 +46,7 @@ server { add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; - add_header Content-Security-Policy "default-src 'self'; img-src 'self' www.gstatic.com www.google.com translate.googleapis.com http://www1.nyc.gov/ data:; style-src 'self' 'unsafe-inline' code.jquery.com ajax.googleapis.com maxcdn.bootstrapcdn.com translate.googleapis.com http://www1.nyc.gov/; script-src 'self' 'unsafe-inline' 'unsafe-eval' translate.google.com translate.googleapis.com ajax.googleapis.com code.jquery.com http://www1.nyc.gov/ https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; font-src 'self' maxcdn.bootstrapcdn.com; object-src 'self' blob ajax.googleapis.com; frame-src 'self' https://www.google.com;"; + add_header Content-Security-Policy "default-src 'self' https://translate.googleapis.com; img-src 'self' https://translate.google.com www.gstatic.com www.google.com translate.googleapis.com http://www1.nyc.gov/ data:; style-src 'self' 'unsafe-inline' https://www.gstatic.com code.jquery.com ajax.googleapis.com maxcdn.bootstrapcdn.com translate.googleapis.com http://www1.nyc.gov/; script-src 'self' 'unsafe-inline' 'unsafe-eval' translate-pa.googleapis.com translate.google.com translate.googleapis.com ajax.googleapis.com code.jquery.com http://www1.nyc.gov/ https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; font-src 'self' maxcdn.bootstrapcdn.com; object-src 'self' blob ajax.googleapis.com; frame-src 'self' https://www.google.com;"; client_max_body_size 20M; @@ -109,8 +109,7 @@ server { add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; - add_header Content-Security-Policy "default-src 'self'; img-src 'self' www.gstatic.com www.google.com translate.googleapis.com http://www1.nyc.gov/ data:; style-src 'self' 'unsafe-inline' code.jquery.com ajax.googleapis.com maxcdn.bootstrapcdn.com translate.googleapis.com http://www1.nyc.gov/; script-src 'self' 'unsafe-inline' 'unsafe-eval' translate.google.com translate.googleapis.com ajax.googleapis.com code.jquery.com http://www1.nyc.gov/ https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; font-src 'self' maxcdn.bootstrapcdn.com; object-src 'self' blob ajax.googleapis.com; frame-src 'self' https://www.google.com;"; - + add_header Content-Security-Policy "default-src 'self'; img-src 'self' www.gstatic.com www.google.com translate.googleapis.com http://www1.nyc.gov/ data:; style-src 'self' 'unsafe-inline' https://www.gstatic.com/ code.jquery.com ajax.googleapis.com maxcdn.bootstrapcdn.com translate.googleapis.com http://www1.nyc.gov/; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://translate-pa.googleapis.com/ translate.google.com translate.googleapis.com ajax.googleapis.com code.jquery.com http://www1.nyc.gov/ https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; font-src 'self' maxcdn.bootstrapcdn.com; object-src 'self' blob ajax.googleapis.com; frame-src 'self' https://www.google.com; style-src-elem 'self' https://www.gstatic.com/; script-src-elem 'self' https://translate-pa.googleapis.com/; connect-src 'self' https://translate.googleapis.com/;"; client_max_body_size 20M; ssl_certificate /vagrant/build_scripts/web_setup/openrecords_v2_0_dev.pem; From 78da8ff29d9ac39fb8a0d5ffb77f930a6f561445 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Thu, 2 Mar 2023 11:11:43 -0500 Subject: [PATCH 43/44] Fixed super user toggle bug --- app/static/js/admin/main.js | 4 +++- app/user/views.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/static/js/admin/main.js b/app/static/js/admin/main.js index 2d1028065..860c9b130 100644 --- a/app/static/js/admin/main.js +++ b/app/static/js/admin/main.js @@ -42,11 +42,13 @@ $(function () { var row = $(this).parents(".row"); var id = row.find("input[name='user-id']").val(); var isSuperUser = $(this).is(":checked"); + var agencyEin = $("input[name='agency-ein']").val(); $.ajax({ url: "/user/" + id, type: "PATCH", data: { - is_super: isSuperUser + is_super: isSuperUser, + agency_ein: agencyEin } }); }); diff --git a/app/user/views.py b/app/user/views.py index 4d7a1754c..63fac3343 100644 --- a/app/user/views.py +++ b/app/user/views.py @@ -316,8 +316,8 @@ def patch(user_id): new_status['agency_ein'] = agency_ein update_object( new_status, - AgencyUsers, - (user_.guid, agency_ein) + Users, + user_.guid ) event_kwargs = { 'request_id': user_.anonymous_request.id if user_.is_anonymous_requester else None, From 52f7b88da9880713a4e1e6355e41662183fbeb49 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Mon, 1 Apr 2024 15:32:14 -0400 Subject: [PATCH 44/44] Fixed typo in models.py --- app/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models.py b/app/models.py index db1ba955e..6cb176a28 100644 --- a/app/models.py +++ b/app/models.py @@ -1019,7 +1019,7 @@ def es_update(self): if self.privacy["title"] else self.title, "agency_name": self.agency.name, - "agency_acronym": self.agency.acronym + "agency_acronym": self.agency.acronym, "request_type": [metadata["form_name"] for metadata in self.custom_metadata.values()] } },