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(' '+i.message+"
"+k+" "+l+'
"),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
+
+
+ {% 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
-
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 %}
+
+{% 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
+
+
+ {% 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
-
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 %}
+
+{% 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.
+
+
"
- }
+ "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
-