diff --git a/.env.template b/.env.template index debb2856..466e0a81 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,4 @@ HOST="127.0.0.1" PORT="3000" HSTS_ENABLE="" +SHARED_SECRET="" diff --git a/.travis.yml b/.travis.yml index 0f54f717..13484dce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,17 @@ language: python -python: - - '3.6' +group: travis_latest -cache: pip -services: mongodb +python: '3.6.5' + +cache: + - pip + +services: + - mongodb + +env: + - PIPENV_IGNORE_VIRTUALENVS=1 install: - cp .env.template .env @@ -13,7 +20,7 @@ install: script: - pipenv run coverage run --source abe -m unittest - - pipenv run flake8 abe + - pipenv run flake8 abe tests *.py after_success: - codecov diff --git a/Pipfile b/Pipfile index 794f5c8e..4c2f24a8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ url = "https://pypi.python.org/simple" verify_ssl = true [requires] -python_full_version = "3.6.4" #https://devcenter.heroku.com/articles/python-runtimes +python_full_version = "3.6.5" #https://devcenter.heroku.com/articles/python-runtimes [packages] amqp = ">=1.4.9" diff --git a/Pipfile.lock b/Pipfile.lock index 00f5b964..e221b19f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,24 @@ { "_meta": { "hash": { - "sha256": "192245ec396e5545d2813ffbe98ff6acf17f2bd090100a74f547a861e6f62357" + "sha256": "a5fc84b23414faed39293cc93ca8b1c01cc2bb72d6127ebd430b12c77495df00" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.5", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.4.0-121-generic", + "platform_system": "Linux", + "platform_version": "#145-Ubuntu SMP Fri Apr 13 13:47:23 UTC 2018", + "python_full_version": "3.6.5", + "python_version": "3.6", + "sys_platform": "linux" }, "pipfile-spec": 6, "requires": { - "python_full_version": "3.6.4" + "python_full_version": "3.6.5" }, "sources": [ { @@ -18,77 +31,68 @@ "default": { "amqp": { "hashes": [ - "sha256:2dea4d16d073c902c3b89d9b96620fb6729ac0f7a923bbc777cb4ad827c0c61a", - "sha256:e0ed0ce6b8ffe5690a2e856c7908dc557e0e605283d6885dd1361d79f2928908" + "sha256:e0ed0ce6b8ffe5690a2e856c7908dc557e0e605283d6885dd1361d79f2928908", + "sha256:2dea4d16d073c902c3b89d9b96620fb6729ac0f7a923bbc777cb4ad827c0c61a" ], - "index": "pypi", "version": "==1.4.9" }, "aniso8601": { "hashes": [ - "sha256:7cf068e7aec00edeb21879c2bbda048656c34d281e133a77425be03b352122d8", - "sha256:f7052eb342bf2000c6264a253acedb362513bf9270800be2bc8e3e229fe08b5a" + "sha256:f7052eb342bf2000c6264a253acedb362513bf9270800be2bc8e3e229fe08b5a", + "sha256:7cf068e7aec00edeb21879c2bbda048656c34d281e133a77425be03b352122d8" ], - "index": "pypi", "version": "==3.0.0" }, "anyjson": { "hashes": [ "sha256:37812d863c9ad3e35c0734c42e0bf0320ce8c3bed82cd20ad54cb34d158157ba" ], - "index": "pypi", "version": "==0.3.3" }, "arrow": { "hashes": [ "sha256:a558d3b7b6ce7ffc74206a86c147052de23d3d4ef0e17c210dd478c53575c4cd" ], - "index": "pypi", "version": "==0.12.1" }, "billiard": { "hashes": [ - "sha256:204e75d390ef8f839c30a93b696bd842c3941916e15921745d05edc2a83868ab", - "sha256:23cb71472712e96bff3e0d45763b7b8a99e5040385fffb96816028352c255682", - "sha256:692a2a5a55ee39a42bcb7557930e2541da85df9ea81c6e24827f63b80cd39d0b", + "sha256:c0cbe8d45ba8d8213ad68ef9a1881002a151569c9424d551634195a18c3a4160", "sha256:82041dbaa62f7fde1464d7ab449978618a38b241b40c0d31dafabb36446635dc", "sha256:958fc9f8fd5cc9b936b2cb9d96f02aa5ec3613ba13ee7f089c77ff0bcc368fac", - "sha256:c0cbe8d45ba8d8213ad68ef9a1881002a151569c9424d551634195a18c3a4160", + "sha256:204e75d390ef8f839c30a93b696bd842c3941916e15921745d05edc2a83868ab", + "sha256:d4d2fed1a251ea58eed47b48db3778ebb92f5ff4407dc91869c6f41c3a9249d0", + "sha256:23cb71472712e96bff3e0d45763b7b8a99e5040385fffb96816028352c255682", "sha256:ccfe0419eb5e49f27ad35cf06e75360af903df6d576c66cb8073246d4e023e5c", - "sha256:d4d2fed1a251ea58eed47b48db3778ebb92f5ff4407dc91869c6f41c3a9249d0" + "sha256:692a2a5a55ee39a42bcb7557930e2541da85df9ea81c6e24827f63b80cd39d0b" ], - "index": "pypi", "version": "==3.3.0.23" }, "celery": { "hashes": [ - "sha256:5493e172ae817b81ba7d09443ada114886765a8ce02f16a56e6fac68d953a9b2", - "sha256:60211897aee321266ff043fe2b33eaac825dfe9f46843cf964fc97507a186334" + "sha256:60211897aee321266ff043fe2b33eaac825dfe9f46843cf964fc97507a186334", + "sha256:5493e172ae817b81ba7d09443ada114886765a8ce02f16a56e6fac68d953a9b2" ], - "index": "pypi", "version": "==3.1.26.post2" }, "celery-with-mongodb": { "hashes": [ "sha256:26e714f31d67c6afde79317280e61ed40c0f470f6c3cf420f0f63e168aa0de0d" ], - "index": "pypi", "version": "==3.0" }, "certifi": { "hashes": [ - "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", - "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0", + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7" ], - "index": "pypi", "version": "==2018.4.16" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" ], - "index": "pypi", "version": "==3.0.4" }, "click": { @@ -96,108 +100,96 @@ "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" ], - "index": "pypi", "version": "==6.7" }, "enum-compat": { "hashes": [ "sha256:939ceff18186a5762ae4db9fa7bfe017edbd03b66526b798dd8245394c8a4192" ], - "index": "pypi", "version": "==0.0.2" }, "eventlet": { "hashes": [ - "sha256:46b7e565aaa06b5d1ba435cb355e09cf3002e34dc269671c93c960f9879d30e0", - "sha256:87b2afb22fb7601f77e9cb9481e3e8c557e8cac9df69b5b2dc0b38ec5c23d67a" + "sha256:87b2afb22fb7601f77e9cb9481e3e8c557e8cac9df69b5b2dc0b38ec5c23d67a", + "sha256:46b7e565aaa06b5d1ba435cb355e09cf3002e34dc269671c93c960f9879d30e0" ], - "index": "pypi", "version": "==0.22.1" }, "flask": { "hashes": [ - "sha256:7fab1062d11dd0038434e790d18c5b9133fd9e6b7257d707c4578ccc1e38b67c", - "sha256:b1883637bbee4dc7bc98d900792d0a304d609fce0f5bd9ca91d1b6457e5918dd" + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05", + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48" ], - "index": "pypi", - "version": "==1.0" + "version": "==1.0.2" }, "flask-cors": { "hashes": [ "sha256:a1bb895b3e98b4325743149ebe0e3a0652537b8e37854f942bde7843f822f129", "sha256:bec996f0603a0693c0ea63c8126e5f8e966bb679cf82e6104b254e9c7f3a7d08" ], - "index": "pypi", "version": "==3.0.4" }, "flask-restful": { "hashes": [ - "sha256:5795519501347e108c436b693ff9a4d7b373a3ac9069627d64e4001c05dd3407", - "sha256:e2f1b8063de3944b94c7f8be5cee4d2161db0267c54c5b757d875295061776fa" + "sha256:e2f1b8063de3944b94c7f8be5cee4d2161db0267c54c5b757d875295061776fa", + "sha256:5795519501347e108c436b693ff9a4d7b373a3ac9069627d64e4001c05dd3407" ], - "index": "pypi", "version": "==0.3.6" }, "flask-restplus": { "hashes": [ - "sha256:129767ba6087a6f0a6d1bd901f4c11192b4d33fe80c38cf2a8a0b5f8dd6049e3", - "sha256:1a005a960ee2efa96dd8c6dc67efb0f047b9c27057a3e9d635181a41548ceba6" + "sha256:1a005a960ee2efa96dd8c6dc67efb0f047b9c27057a3e9d635181a41548ceba6", + "sha256:129767ba6087a6f0a6d1bd901f4c11192b4d33fe80c38cf2a8a0b5f8dd6049e3" ], - "index": "pypi", "version": "==0.10.1" }, "flask-socketio": { "hashes": [ - "sha256:be85842328f7847f511cf8cd828739884403b86ab5b0d576dae4241dd920b415", - "sha256:f49edfd3a44458fbb9f7a04a57069ffc0c37f000495194f943a25d370436bb69" + "sha256:49c42696f27c100289219dde35422b6dec45c5b15f8990250e951fb54296c555", + "sha256:388e0b12d66ea2ca0519a4a8d1cbeb90feb5a98034b44220f256014aad7c5e89" ], - "index": "pypi", - "version": "==2.9.6" + "version": "==3.0.0" }, "flask-sslify": { "hashes": [ "sha256:d33e1d3c09cd95154176aa8a7319418e52129fc482dd56d8a8ad7c24500d543e" ], - "index": "pypi", "version": "==0.1.5" }, "greenlet": { "hashes": [ - "sha256:09ef2636ea35782364c830f07127d6c7a70542b178268714a9a9ba16318e7e8b", - "sha256:0fef83d43bf87a5196c91e73cb9772f945a4caaff91242766c5916d1dd1381e4", - "sha256:1b7df09c6598f5cfb40f843ade14ed1eb40596e75cd79b6fa2efc750ba01bb01", - "sha256:1fff21a2da5f9e03ddc5bd99131a6b8edf3d7f9d6bc29ba21784323d17806ed7", - "sha256:42118bf608e0288e35304b449a2d87e2ba77d1e373e8aa221ccdea073de026fa", "sha256:50643fd6d54fd919f9a0a577c5f7b71f5d21f0959ab48767bd4bb73ae0839500", - "sha256:58798b5d30054bb4f6cf0f712f08e6092df23a718b69000786634a265e8911a9", - "sha256:5b49b3049697aeae17ef7bf21267e69972d9e04917658b4e788986ea5cc518e8", - "sha256:75c413551a436b462d5929255b6dc9c0c3c2b25cbeaee5271a56c7fda8ca49c0", - "sha256:769b740aeebd584cd59232be84fdcaf6270b8adc356596cdea5b2152c82caaac", - "sha256:ad2383d39f13534f3ca5c48fe1fc0975676846dc39c2cece78c0f1f9891418e0", - "sha256:b417bb7ff680d43e7bd7a13e2e08956fa6acb11fd432f74c97b7664f8bdb6ec1", + "sha256:c7b04a6dc74087b1598de8d713198de4718fa30ec6cbb84959b26426c198e041", "sha256:b6ef0cabaf5a6ecb5ac122e689d25ba12433a90c7b067b12e5f28bdb7fb78254", + "sha256:fcfadaf4bf68a27e5dc2f42cbb2f4b4ceea9f05d1d0b8f7787e640bed2801634", + "sha256:b417bb7ff680d43e7bd7a13e2e08956fa6acb11fd432f74c97b7664f8bdb6ec1", + "sha256:769b740aeebd584cd59232be84fdcaf6270b8adc356596cdea5b2152c82caaac", "sha256:c2de19c88bdb0366c976cc125dca1002ec1b346989d59524178adfd395e62421", - "sha256:c7b04a6dc74087b1598de8d713198de4718fa30ec6cbb84959b26426c198e041", + "sha256:5b49b3049697aeae17ef7bf21267e69972d9e04917658b4e788986ea5cc518e8", + "sha256:09ef2636ea35782364c830f07127d6c7a70542b178268714a9a9ba16318e7e8b", "sha256:f8f2a0ae8de0b49c7b5b2daca4f150fdd9c1173e854df2cce3b04123244f9f45", - "sha256:fcfadaf4bf68a27e5dc2f42cbb2f4b4ceea9f05d1d0b8f7787e640bed2801634" + "sha256:1b7df09c6598f5cfb40f843ade14ed1eb40596e75cd79b6fa2efc750ba01bb01", + "sha256:75c413551a436b462d5929255b6dc9c0c3c2b25cbeaee5271a56c7fda8ca49c0", + "sha256:58798b5d30054bb4f6cf0f712f08e6092df23a718b69000786634a265e8911a9", + "sha256:42118bf608e0288e35304b449a2d87e2ba77d1e373e8aa221ccdea073de026fa", + "sha256:ad2383d39f13534f3ca5c48fe1fc0975676846dc39c2cece78c0f1f9891418e0", + "sha256:1fff21a2da5f9e03ddc5bd99131a6b8edf3d7f9d6bc29ba21784323d17806ed7", + "sha256:0fef83d43bf87a5196c91e73cb9772f945a4caaff91242766c5916d1dd1381e4" ], - "index": "pypi", "version": "==0.4.13" }, "gunicorn": { "hashes": [ - "sha256:eb8d8924b117a609fae9f8cd85df0cad3535dd613fdbcdbba3ee88d5459f1d4f", - "sha256:f5ca088d029fe3cea166c59bb43b7ccc9c850fe25af3da61350fe712c5cc5aa2" + "sha256:7ef2b828b335ed58e3b64ffa84caceb0a7dd7c5ca12f217241350dec36a1d5dc", + "sha256:bc59005979efb6d2dd7d5ba72d99f8a8422862ad17ff3a16e900684630dd2a10" ], - "index": "pypi", - "version": "==19.8.0" + "version": "==19.8.1" }, "honcho": { "hashes": [ "sha256:af5806bf13e3b20acdcb9ff8c0beb91eee6fe07393c3448dfad89667e6ac7576", "sha256:c189402ad2e337777283c6a12d0f4f61dc6dd20c254c9a3a4af5087fc66cea6e" ], - "index": "pypi", "version": "==1.0.1" }, "icalendar": { @@ -205,30 +197,26 @@ "sha256:396ad75bc6c7cc23c97c69370a81c001aca06cf99da32e6bb40856ea0bdf7368", "sha256:682a42023d3d43a3a83933b4e329d109aabb07c9e11cb94a4d83ca687c3a3e8d" ], - "index": "pypi", "version": "==4.0.1" }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" ], - "index": "pypi", "version": "==2.6" }, "isodate": { "hashes": [ - "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", - "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81" + "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81", + "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8" ], - "index": "pypi", "version": "==0.6.0" }, "itsdangerous": { "hashes": [ "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" ], - "index": "pypi", "version": "==0.24" }, "jinja2": { @@ -236,7 +224,6 @@ "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" ], - "index": "pypi", "version": "==2.10" }, "jsonschema": { @@ -251,14 +238,12 @@ "sha256:7ceab743e3e974f3e5736082e8cc514c009e254e646d6167342e0e192aee81a6", "sha256:e064a00c66b4d1058cd2b0523fb8d98c82c18450244177b6c0f7913016642650" ], - "index": "pypi", "version": "==3.0.37" }, "markupsafe": { "hashes": [ "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" ], - "index": "pypi", "version": "==1.0" }, "mongoengine": { @@ -266,64 +251,61 @@ "sha256:996e0aba34e0be3834cba19a2ac77aaeebbd7b595b6b896266a2aa41cad3566e", "sha256:f8fed3871dfcefd421d3a3be1b43b3b9a1622db6a4271d78f15f494e51003f75" ], - "index": "pypi", "version": "==0.15.0" }, "netaddr": { "hashes": [ - "sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd", - "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca" + "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca", + "sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd" ], - "index": "pypi", "version": "==0.7.19" }, "pymongo": { "hashes": [ - "sha256:051770590ddbd5fb7db17d3315d4c1b0f18039d830dd18e1bae39451c30d31cd", - "sha256:061085dfe4fbf1d9d6ed2f2e52fe6ab72559e48b4294370b433751638160d10b", - "sha256:07fdee1c5567f237796a8550233e04853785d8dcf95929f96ab519ed91543109", - "sha256:0d98731aaea8cb32b535c376f6785927e4e3d9459ffe1440b8a639827a849350", - "sha256:10f683950f70626ccedf4a662d1c0b3244e8e013c2067872af5633830abd1bfd", - "sha256:192ee5e33821931f4ec6df5fff4361220c0c92bb5b7437c6db52e20a0c9b4d98", - "sha256:2954b99cfeb76776879e9f8a4cae9c5e19d5eff92d0b7b663ceddcf192adb66b", - "sha256:36a992e02fced328de5304145dc3729a8cea12e58ad34b842a6f46d7941c9fc7", - "sha256:419ed5d5b76ef304815f354d9df7f2085acfd6ff7cc1b714ca702e2239b341c2", + "sha256:dd29bb5bc9068ccc248c8c145efd839421f04363b468b47cfa2d4902ca369afe", + "sha256:e53ad0cc6c489f83e7f6bb6121aa73bb6f6488410024a3bd77c16af1aa3a1000", + "sha256:abf83b908e535b1386a7732825994e6e36eff6394c1829f3e7a23888136484fa", + "sha256:ecb11113407d919f8714cc7d0841985044633d0b561ef3d797e1b494a3e73537", "sha256:42ec201fd9a26e7c1e611e3db19324dead51dd4646391492eb238b41749340e8", - "sha256:4400fa92af310bf66b76c313c7ded3bb63f3d63b4f43c3bfbff552cf294dc9fa", - "sha256:44abdc26989600bb03b62d57616ec7c1b9182290720167c39e38c3a2b0d44e44", - "sha256:45fb9f589c0f35436dbe391c53a387ffffa8d086b8521a86fca4f3e1d0edbf71", - "sha256:4807dfbb5cdcfe0224329992dc48b897c780d0ad7553c3799d34f84ba5cab446", + "sha256:ece2c2add66d3ec2720a963bf073ca11fc3b0b58159767fc3bc5ddaad791d481", + "sha256:92cb26a2a9b38e8df5215803f950b20a6c847d5e00d1dd125eaa84f05f9472d7", "sha256:54daf67e1e7e7e5a5160c86123bdd39b1d3b25876c2ab38230dc2a764cb3d98f", - "sha256:5f2814a9492a724fd77c90ffc01f810276ef9972ae02587bfaae40835f9b8407", + "sha256:10f683950f70626ccedf4a662d1c0b3244e8e013c2067872af5633830abd1bfd", + "sha256:419ed5d5b76ef304815f354d9df7f2085acfd6ff7cc1b714ca702e2239b341c2", "sha256:5fd6ce5ed3c6c92d2c94756e6bf041304e5c7c5a5dbea31b8957d52a78bdf01d", - "sha256:601e00fe7fb283f04c95f5dafb787c0862f48ca015a6f1f81b460c74e4303873", - "sha256:63a47a97b5cb4c67c86552b15e08df12ff026a648211120adf5ebe00453e85e9", - "sha256:6c4459d5c2b45ba55e14360e03078426015c1b0881facaec51bd9bd9e2304cec", + "sha256:adb2dba52c8a2a2d7bcd3b267f7bbf7c822850cf6a7cd15211b9f386c3a670ef", "sha256:7fbd9233e8b6741b047c5857e2ad5efb74091f167d7fa8a2a3379217165058f9", "sha256:7ffac35362c07c103b024b89875e8d7f0625129b65c56fa8a3ecebbd56110405", + "sha256:ae7b3479822a03f6f651913de84ba67101f23e051ae88034085e974f472dcfff", + "sha256:0d98731aaea8cb32b535c376f6785927e4e3d9459ffe1440b8a639827a849350", + "sha256:cc15b30f0ac518e6cbd4b6e6e6162f8aa14edfe255d0841146f146151bd58865", + "sha256:061085dfe4fbf1d9d6ed2f2e52fe6ab72559e48b4294370b433751638160d10b", + "sha256:2954b99cfeb76776879e9f8a4cae9c5e19d5eff92d0b7b663ceddcf192adb66b", + "sha256:601e00fe7fb283f04c95f5dafb787c0862f48ca015a6f1f81b460c74e4303873", + "sha256:07fdee1c5567f237796a8550233e04853785d8dcf95929f96ab519ed91543109", + "sha256:4400fa92af310bf66b76c313c7ded3bb63f3d63b4f43c3bfbff552cf294dc9fa", + "sha256:d23498d62063b715078947bef48fa4d34dc354f3b268ed15dc6b46fc809a88e9", + "sha256:9e5f0e8967d95a256038817460844a8aab588b9bc9ba6296507a1863960a0e44", + "sha256:f62a818d643776873713c5676f17bd95ac4176220b13dd12c14edd3a450d1ac9", + "sha256:ef25c8675f5c8c19832f69cd97d728d99bb4ab9c3b200e28a5c8416631afaf3c", "sha256:833bc6cb2ec7058dea9f5840a9314ac74738d2117486a044e88f3976e37ea7a0", - "sha256:92cb26a2a9b38e8df5215803f950b20a6c847d5e00d1dd125eaa84f05f9472d7", + "sha256:4807dfbb5cdcfe0224329992dc48b897c780d0ad7553c3799d34f84ba5cab446", + "sha256:44abdc26989600bb03b62d57616ec7c1b9182290720167c39e38c3a2b0d44e44", + "sha256:192ee5e33821931f4ec6df5fff4361220c0c92bb5b7437c6db52e20a0c9b4d98", + "sha256:6c4459d5c2b45ba55e14360e03078426015c1b0881facaec51bd9bd9e2304cec", + "sha256:36a992e02fced328de5304145dc3729a8cea12e58ad34b842a6f46d7941c9fc7", + "sha256:5f2814a9492a724fd77c90ffc01f810276ef9972ae02587bfaae40835f9b8407", + "sha256:c596af57286ef28cae7a48e3070d222f96f5f0eab76ad39d680ae6b9bbc957c7", + "sha256:aa46076524471729430afacca3dd8ad4578878eca6fc9e2b593a0b381b5bbeb7", "sha256:97d6a218c4ad4f8fdde0143776d5224e884cbcfe631e7446379fa1790d8cf04f", - "sha256:9e5f0e8967d95a256038817460844a8aab588b9bc9ba6296507a1863960a0e44", "sha256:9e6db7ff63fb836d56e62216e10e868c23a99f3cb02875411eb2cb787acf58c7", + "sha256:63a47a97b5cb4c67c86552b15e08df12ff026a648211120adf5ebe00453e85e9", "sha256:a0a695eef38c15570f6da3b4900e1a1d85fa92c754177d5f05267b49da79c92b", - "sha256:aa46076524471729430afacca3dd8ad4578878eca6fc9e2b593a0b381b5bbeb7", - "sha256:abf83b908e535b1386a7732825994e6e36eff6394c1829f3e7a23888136484fa", - "sha256:adb2dba52c8a2a2d7bcd3b267f7bbf7c822850cf6a7cd15211b9f386c3a670ef", - "sha256:ae7b3479822a03f6f651913de84ba67101f23e051ae88034085e974f472dcfff", - "sha256:c596af57286ef28cae7a48e3070d222f96f5f0eab76ad39d680ae6b9bbc957c7", - "sha256:cc15b30f0ac518e6cbd4b6e6e6162f8aa14edfe255d0841146f146151bd58865", - "sha256:d23498d62063b715078947bef48fa4d34dc354f3b268ed15dc6b46fc809a88e9", - "sha256:dd29bb5bc9068ccc248c8c145efd839421f04363b468b47cfa2d4902ca369afe", "sha256:e2745dd408a26d4517702d1686afc8e1e1638d2167e857c684f912192cc00dcf", - "sha256:e53ad0cc6c489f83e7f6bb6121aa73bb6f6488410024a3bd77c16af1aa3a1000", - "sha256:ecb11113407d919f8714cc7d0841985044633d0b561ef3d797e1b494a3e73537", - "sha256:ece2c2add66d3ec2720a963bf073ca11fc3b0b58159767fc3bc5ddaad791d481", - "sha256:ef25c8675f5c8c19832f69cd97d728d99bb4ab9c3b200e28a5c8416631afaf3c", - "sha256:f62a818d643776873713c5676f17bd95ac4176220b13dd12c14edd3a450d1ac9", + "sha256:45fb9f589c0f35436dbe391c53a387ffffa8d086b8521a86fca4f3e1d0edbf71", + "sha256:051770590ddbd5fb7db17d3315d4c1b0f18039d830dd18e1bae39451c30d31cd", "sha256:f7ebcb846962ee40374db2d9014a89bea9c983ae63c1877957c3a0a756974796" ], - "index": "pypi", "version": "==3.6.1" }, "python-dateutil": { @@ -331,23 +313,20 @@ "sha256:3220490fb9741e2342e1cf29a503394fdac874bc39568288717ee67047ff29df", "sha256:9d8074be4c993fbe4947878ce593052f71dac82932a677d49194d8ce9778002e" ], - "index": "pypi", "version": "==2.7.2" }, "python-engineio": { "hashes": [ - "sha256:8a4ebbe53af5123a7057f29677dbe78a8515d3b7e5e124431d0542886d54d013", - "sha256:cf73086278158307535ad94fd2f9c53becf57790c699938006b2dc2be72ec44a" + "sha256:cf73086278158307535ad94fd2f9c53becf57790c699938006b2dc2be72ec44a", + "sha256:8a4ebbe53af5123a7057f29677dbe78a8515d3b7e5e124431d0542886d54d013" ], - "index": "pypi", "version": "==2.1.0" }, "python-socketio": { "hashes": [ - "sha256:71feb10a1b7b410d8f86bbcb0f589c75a32504a01259ed89aa1d7d5dae9190cc", - "sha256:93f59452416b00ffd04a4f7d95139235e5eb8876cb090aa939a8e7807fc3ebba" + "sha256:93f59452416b00ffd04a4f7d95139235e5eb8876cb090aa939a8e7807fc3ebba", + "sha256:71feb10a1b7b410d8f86bbcb0f589c75a32504a01259ed89aa1d7d5dae9190cc" ], - "index": "pypi", "version": "==1.9.0" }, "pytz": { @@ -355,7 +334,6 @@ "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" ], - "index": "pypi", "version": "==2018.4" }, "requests": { @@ -363,13 +341,12 @@ "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" ], - "index": "pypi", "version": "==2.18.4" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" ], "version": "==1.11.0" }, @@ -378,99 +355,91 @@ "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" ], - "index": "pypi", "version": "==1.22" }, "werkzeug": { "hashes": [ - "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", - "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" + "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b", + "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c" ], - "index": "pypi", "version": "==0.14.1" } }, "develop": { "certifi": { "hashes": [ - "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", - "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0", + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7" ], - "index": "pypi", "version": "==2018.4.16" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" ], - "index": "pypi", "version": "==3.0.4" }, "codecov": { "hashes": [ - "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788", - "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4" + "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4", + "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788" ], - "index": "pypi", "version": "==2.0.15" }, "coverage": { "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", - "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" ], - "index": "pypi", "version": "==4.5.1" }, "flake8": { "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37", + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0" ], - "index": "pypi", "version": "==3.5.0" }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" ], - "index": "pypi", "version": "==2.6" }, "mccabe": { @@ -478,17 +447,15 @@ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" ], - "index": "pypi", "version": "==0.6.1" }, "pycodestyle": { "hashes": [ - "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14", "sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e", - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", + "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14", + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" ], - "index": "pypi", "version": "==2.3.1" }, "pyflakes": { @@ -496,7 +463,6 @@ "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" ], - "index": "pypi", "version": "==1.6.0" }, "requests": { @@ -504,7 +470,6 @@ "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" ], - "index": "pypi", "version": "==2.18.4" }, "urllib3": { @@ -512,7 +477,6 @@ "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" ], - "index": "pypi", "version": "==1.22" } } diff --git a/README.md b/README.md index 7891ceb2..1ac1063c 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ $ open htmlcov/index.html Lint all the things: ```shell -$ flake8 abe *.py +$ flake8 abe tests *.py ``` ## API Documentation diff --git a/abe/app.py b/abe/app.py index 96a10939..23663593 100644 --- a/abe/app.py +++ b/abe/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Main flask app""" -from flask import Flask, render_template, jsonify +from flask import Flask, render_template, jsonify, g from flask_restplus import Api from flask_cors import CORS from flask_sslify import SSLify # redirect to https @@ -72,6 +72,13 @@ def output_json(data, code, headers=None): return resp +@app.after_request +def call_after_request_callbacks(response): # For deferred callbacks + for callback in getattr(g, 'after_request_callbacks', ()): + callback(response) + return response + + # Route resources api.add_namespace(event_api) api.add_namespace(label_api) diff --git a/abe/auth/__init__.py b/abe/auth/__init__.py index 1228b2f1..319f81c2 100644 --- a/abe/auth/__init__.py +++ b/abe/auth/__init__.py @@ -3,7 +3,7 @@ import os from functools import wraps -from flask import request, abort +from flask import request, abort, g from netaddr import IPNetwork, IPSet # A set of IP addresses with edit permission. @@ -17,14 +17,38 @@ INTRANET_IPS = (IPSet([IPNetwork(s) for s in os.environ.get('INTRANET_IPS', '').split(',')]) if 'INTRANET_IPS' in os.environ else IPSet(['0.0.0.0/0', '0000:000::/0'])) +shared_secret = os.environ.get("SHARED_SECRET", '') + + +def after_this_request(f): # For setting cookie + if not hasattr(g, 'after_request_callbacks'): + g.after_request_callbacks = [] + g.after_request_callbacks.append(f) + return f + + +def check_auth(req): + """ + Checks if a request is from an IP whitelist, or if it has a secret cookie. + If the request is in the IP whitelist, sets the secret cookie. + Returns a Bool of passing. + """ + client_ip = req.headers.get( + 'X-Forwarded-For', req.remote_addr).split(',')[-1] + if client_ip in INTRANET_IPS: + @after_this_request + def remember_computer(response): + response.set_cookie('app_secret', shared_secret) + return True + else: + return bool(shared_secret) and (req.cookies.get('app_secret') == shared_secret) + def edit_auth_required(f): - "Decorates f to raise an HTTP UNAUTHORIZED exception if the client IP is not in the list of authorized IPs." + "Decorates f to raise an HTTP UNAUTHORIZED exception if the auth check fails." @wraps(f) def wrapped(*args, **kwargs): - client_ip = request.headers.get( - 'X-Forwarded-For', request.remote_addr).split(',')[-1] - if client_ip not in INTRANET_IPS: + if not check_auth(request): abort(401) return f(*args, **kwargs) return wrapped diff --git a/abe/helper_functions/email_helpers.py b/abe/helper_functions/email_helpers.py index b6366036..927fa47b 100644 --- a/abe/helper_functions/email_helpers.py +++ b/abe/helper_functions/email_helpers.py @@ -7,6 +7,8 @@ import icalendar as ic from mongoengine import ValidationError +from datetime import datetime as dt +from email.message import EmailMessage from abe import database as db from abe.helper_functions.converting_helpers import mongo_to_dict @@ -17,6 +19,7 @@ ABE_EMAIL_PASSWORD = os.environ.get('ABE_EMAIL_PASSWORD', None) ABE_EMAIL_HOST = os.environ.get('ABE_EMAIL_HOST', 'mail.privateemail.com') ABE_EMAIL_PORT = int(os.environ.get('ABE_EMAIL_PORT', 465)) +APP_URL = os.environ.get('APP_URL', 'events.olin.build') def get_msg_list(pop_items, pop_conn): @@ -159,45 +162,38 @@ def error_reply(to, error): """ Given the error, sends an email with the errors that occured to the original sender. """ server, sent_from = smtp_connect() - subject = 'Event Failed to Add' + msg = EmailMessage() body = "ABE didn't manage to add the event, sorry. Here's what went wrong: \n" for err in error.errors: body = body + str(err) + '\n' body = body + "Final error message: " + error.message - - email_text = """ - From: {} - To: {} - Subject: {} - - {} - """.format(sent_from, to, subject, body) - - send_email(server, email_text, sent_from, to) + msg['Subject'] = 'Event Failed to Add' + msg['From'] = sent_from + msg['To'] = [to] + msg.set_content(body) + server.send_message(msg) + server.close() def reply_email(to, event_dict): """ Responds after a successful posting with the tags under which the event was saved. """ server, sent_from = smtp_connect() - subject = '{} added to ABE!'.format(event_dict['title']) tags = ', '.join(event_dict['labels']).strip() - body = "Your event was added to ABE! Here's the details: " - - email_text = """ - From: {} - To: {} - Subject: {} - - {} - Description: {} - Tags: {} - """.format(sent_from, to, subject, body, event_dict['description'], tags) - send_email(server, email_text, sent_from, to) - - -def send_email(server, email_text, sent_from, sent_to): - server.sendmail(sent_from, sent_to, email_text.encode('utf-8')) + start = dt.strptime(event_dict['start'][:16], '%Y-%m-%d %H:%M').strftime('%I:%M %m/%d') + end = dt.strptime(event_dict['end'][:16], '%Y-%m-%d %H:%M').strftime('%I:%M %m/%d') + body = f"""Your event was added to ABE! Here's the details: + Time: {start} to {end} + Description: {event_dict['description']} + Tags: {tags} + Something wrong? Edit this event at {APP_URL}/edit/{event_dict['id']} + """ + msg = EmailMessage() + msg['Subject'] = f"{event_dict['title']} added to ABE!" + msg['From'] = sent_from + msg['To'] = [to] + msg.set_content(body) + server.send_message(msg) server.close() diff --git a/abe/helper_functions/ics_helpers.py b/abe/helper_functions/ics_helpers.py index 149a3780..f0635f46 100644 --- a/abe/helper_functions/ics_helpers.py +++ b/abe/helper_functions/ics_helpers.py @@ -198,9 +198,9 @@ def convert_timezone(a): event_def['end'].replace(hour=23, minute=59, second=59) elif isinstance(event_def['end'], datetime.date): - event_def['end'] = event_def['end'] - timedelta(day=1) + event_def['end'] = event_def['end'] - timedelta(days=1) midnight_time = time(23, 59, 59) - event_def['end'] = datetime.combine(event_def['end'], midnight_time) + event_def['end'] = datetime.datetime.combine(event_def['end'], midnight_time) event_def['allDay'] = True event_def['labels'] = labels @@ -253,7 +253,7 @@ def extract_ics(cal, ics_url, labels=None): for component in cal.walk(): if component.name == "VEVENT": last_modified = component.get('LAST-MODIFIED').dt - now = datetime.now(timezone.utc) + now = datetime.datetime.now(timezone.utc) difference = now - last_modified # if an event has been modified in the last two hours if difference.total_seconds() < 7200: @@ -278,11 +278,11 @@ def extract_ics(cal, ics_url, labels=None): temporary_dict.append(com_dict) logging.debug("temporarily saved recurring event as dict") else: # if this is a regular event - com_dict['self'] = open('README.md') try: new_event = db.Event(**com_dict).save() except: # FIXME: bare except # noqa: E722 - logging.exception("com_dict: {}", com_dict) + logging.exception("com_dict: {}".format(com_dict)) + continue if not new_event.labels: # if the event has no labels new_event.labels = ['unlabeled'] if 'recurrence' in new_event: # if the event has no recurrence_end diff --git a/abe/resource_models/label_resources.py b/abe/resource_models/label_resources.py index eb7cc01a..cbbb3516 100644 --- a/abe/resource_models/label_resources.py +++ b/abe/resource_models/label_resources.py @@ -32,7 +32,7 @@ class LabelApi(Resource): def get(self, label_name=None): """Retrieve labels""" if label_name: # use label name/object id if present - logging.debug('Label requested: ' + label_name) + logging.debug('Label requested: %s', label_name) search_fields = ['name', 'id'] result = multi_search(db.Label, label_name, search_fields) if not result: @@ -52,12 +52,12 @@ def get(self, label_name=None): def post(self): """Create new label with parameters passed in through args or form""" received_data = request_to_dict(request) - logging.debug("Received POST data: {}".format(received_data)) + logging.debug("Received POST data: %s", received_data) try: new_label = db.Label(**received_data) new_label.save() except ValidationError as error: - logging.warning("POST request rejected: {}".format(str(error))) + logging.debug("POST request rejected: %s", error) return {'error_type': 'validation', 'validation_errors': [str(err) for err in error.errors], 'error_message': error.message}, 400 @@ -69,7 +69,7 @@ def post(self): def put(self, label_name): """Modify individual label""" received_data = request_to_dict(request) - logging.debug("Received PUT data: {}".format(received_data)) + logging.debug("Received PUT data: %s", received_data) search_fields = ['name', 'id'] result = multi_search(db.Label, label_name, search_fields) if not result: @@ -88,14 +88,14 @@ def put(self, label_name): @edit_auth_required def delete(self, label_name): """Delete individual label""" - logging.debug('Label requested: ' + label_name) + logging.debug('Label requested: %s', label_name) search_fields = ['name', 'id'] result = multi_search(db.Label, label_name, search_fields) if not result: return "Label not found with identifier '{}'".format(label_name), 404 received_data = request_to_dict(request) - logging.debug("Received DELETE data: {}".format(received_data)) + logging.debug("Received DELETE data: %s", received_data) result.delete() return mongo_to_dict(result) diff --git a/abe/sample_data.py b/abe/sample_data.py index fa8da4cc..ac4356d1 100644 --- a/abe/sample_data.py +++ b/abe/sample_data.py @@ -13,6 +13,7 @@ sample_data_dir = Path(__file__).parent.parent / 'tests/data' sample_events_file = sample_data_dir / 'sample-events.json' sample_labels_file = sample_data_dir / 'sample-labels.json' +sample_ics_file = sample_data_dir / 'sample-ics-import.ics' kelly_colors = [ '#F2F3F4', '#222222', '#F3C300', '#875692', '#F38400', '#A1CAF1', '#BE0032', @@ -39,7 +40,8 @@ sample_ics = [ { - "url": "webcal://http://www.olin.edu/calendar-node-field-cal-event-date/ical/calendar.ics" + "url": "http://www.olin.edu/calendar-node-field-cal-event-date/ical/2018-05/calendar.ics", + "labels": ['Featured'], } ] @@ -112,7 +114,7 @@ def insert_data(db, event_data=None, label_data=None, ics_data=None): db.ICS(**ics).save() -SampleData = namedtuple('SampleData', ['events', 'labels', 'icss']) +SampleData = namedtuple('SampleData', ['events', 'labels', 'icss', 'ics_data']) def load_sample_data(): @@ -124,13 +126,15 @@ def load_sample_data(): event_data = load_event_data(fp) with open(sample_labels_file) as fp: label_data = json.load(fp) - return SampleData(event_data, label_data, sample_ics) + with open(sample_ics_file) as fp: + ics_data = fp.read() + return SampleData(event_data, label_data, sample_ics, ics_data) def load_data(db): """Load the database with sample data. The Heroku postdeploy script calls this.""" - event_data, label_data, sample_ics = load_sample_data() + event_data, label_data, sample_ics, _ = load_sample_data() insert_data(db, event_data, label_data, sample_ics) @@ -147,7 +151,7 @@ def main(events=None, labels=None): label_data = json.load(labels) if labels else None ics_data = None if all(data is None for data in (event_data, label_data, ics_data)): - event_data, label_data, ics_data = load_sample_data() + event_data, label_data, ics_data, _ = load_sample_data() insert_data(db, event_data, label_data, ics_data) diff --git a/app.json b/app.json index deb1a76a..d7124d5b 100644 --- a/app.json +++ b/app.json @@ -26,6 +26,10 @@ "description": "Enforce SSL via HSTS", "value": "True", "required": true + }, + "SHARED_SECRET": { + "description": "A shared secret for an authorized user cookie", + "generator": "secret" } }, "formation": { diff --git a/celeryconfig.py b/celeryconfig.py index 0d3f8e66..b1fbed6f 100644 --- a/celeryconfig.py +++ b/celeryconfig.py @@ -1,23 +1,24 @@ -from celery.schedules import crontab from datetime import timedelta + ''' CELERY_RESULT_BACKEND = "mongodb" CELERY_MONGODB_BACKEND_SETTINGS = { "host": "127.0.0.1", "port": 27017, - "database": "jobs", + "database": "jobs", "taskmeta_collection": "stock_taskmeta_collection", } ''' -#used to schedule tasks periodically and passing optional arguments -#Can be very useful. Celery does not seem to support scheduled task but only periodic + +# used to schedule tasks periodically and passing optional arguments +# Can be very useful. Celery does not seem to support scheduled task but only periodic CELERYBEAT_SCHEDULE = { 'refresh-every-2-hours': { 'task': 'tasks.refresh_calendar', 'schedule': timedelta(seconds=7200), }, 'refresh-every-minute': { - 'task':'tasks.parse_email_icals', - 'schedule': timedelta(seconds=60), + 'task': 'tasks.parse_email_icals', + 'schedule': timedelta(seconds=60), } -} \ No newline at end of file +} diff --git a/tasks.py b/tasks.py index 82c21405..8ad49c8b 100644 --- a/tasks.py +++ b/tasks.py @@ -1,24 +1,27 @@ +import logging + from celery import Celery -from abe.helper_functions.ics_helpers import update_ics_feed -from abe.helper_functions.email_helpers import scrape + from abe import database as db -import time -import logging +from abe.helper_functions.email_helpers import scrape +from abe.helper_functions.ics_helpers import update_ics_feed -#Specify mongodb host and datababse to connect to +# Specify mongodb host and datababse to connect to BROKER_URL = db.return_uri() -celery = Celery('EOD_TASKS',broker=BROKER_URL) +celery = Celery('EOD_TASKS', broker=BROKER_URL) -#Loads settings for Backend to store results of jobs +# Loads settings for Backend to store results of jobs celery.config_from_object('celeryconfig') + @celery.task def refresh_calendar(): - update_ics_feed() - return("did the stuff") + update_ics_feed() + logging.info("updated the ICS feed") + @celery.task def parse_email_icals(): - completed_events = scrape() - return("scraped the emails") \ No newline at end of file + scrape() + logging.info("scraped the emails") diff --git a/tests/abe_unittest.py b/tests/abe_unittest.py index 563968dc..2d3596ba 100644 --- a/tests/abe_unittest.py +++ b/tests/abe_unittest.py @@ -1,4 +1,3 @@ -import os import unittest from pymongo import MongoClient diff --git a/tests/context.py b/tests/context.py index e6eee524..39ab9dcc 100644 --- a/tests/context.py +++ b/tests/context.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Provide context for imports of ABE. Referenced from The Hitchhiker's Guide to Python. http://docs.python-guide.org/en/latest/writing/structure/#test-suite @@ -10,6 +9,7 @@ MONGODB_TEST_DB_NAME = "abe-unittest" os.environ["DB_NAME"] = MONGODB_TEST_DB_NAME +os.environ["INTRANET_IPS"] = "127.0.0.1/24" os.environ["MONGO_URI"] = "" os.environ['ABE_EMAIL_USERNAME'] = 'email-test-user' diff --git a/tests/data/sample-ics-import.ics b/tests/data/sample-ics-import.ics new file mode 100644 index 00000000..1ca94a52 --- /dev/null +++ b/tests/data/sample-ics-import.ics @@ -0,0 +1,233 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Date iCal v3.1//NONSGML kigkonsult.se iCalcreator 2.18// +METHOD:PUBLISH +X-WR-CALNAME;VALUE=TEXT:Event Calendar +BEGIN:VTIMEZONE +TZID:America/New_York +BEGIN:STANDARD +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:calendar.20101.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T180236Z +DESCRIPTION:Return To News & Events [1]\n\n07 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Monday\, May 7 -- 8 to 9 AM\, AC 109 and AC 113\n\nThere will be st + udent demos of robotic vacuum cleaners completing various challenges\, in + cluding crossing the bridge of death\, climbing mount doom\, and running + the gauntlet. \n\n.... For More info\n\nJohn \n\n Geddes John.Geddes@olin.ed + u\n\n\n[1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/events + /feed/20101/event.ics +DTSTART;TZID=America/New_York:20180507T080000 +DTEND;TZID=America/New_York:20180507T080000 +LAST-MODIFIED:20180501T180236Z +SUMMARY:QEA Final Demos +URL;TYPE=URI:http://www.olin.edu/events/qea-final-demos/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20106.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T180636Z +DESCRIPTION:Return To News & Events [1]\n\n08 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Tuesday\, May 8 -- 4 to 5:30 PM\, AC 213\n\nADE teams of Olin\, Bab + son\, and Wellesley students will be holding a presentation and poster se + ssion on their social venture work.\n\nAll are invited.\n\nRSVP here https + ://goo.gl/forms/mbdhXx8sVzqm5qUs2 [3].\n\nLearn how they are planning to t + ransform Reggie Wong Memorial Park in Boston's Chinatown to improve air q + uality\, disseminating hundreds of solar ray projectors in Ghana and Indi + a to create hands-on STEM experiences for children\, running an after sch + ool program in Clarksdale Mississippi's rural communities to create oppor + tunity for youth\, expanding screw press production with QueenTech to equ + ip women entrepreneurs in Ghana\, and developing a conductive warming bas + sinet headed to medical trials in Vietnam to treat newborn hypothermia.  + \n\n.... For More info\n\nBenjamin \n\n Linder Benjamin.Linder@olin.edu\n\n + \n[1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/events/feed + /20106/event.ics [3] https://goo.gl/forms/mbdhXx8sVzqm5qUs2 +DTSTART;TZID=America/New_York:20180508T160000 +DTEND;TZID=America/New_York:20180508T160000 +LAST-MODIFIED:20180501T180636Z +SUMMARY:ADE Social Venture Showcase +URL;TYPE=URI:http://www.olin.edu/events/ade-social-venture-showcase/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20111.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T181135Z +DESCRIPTION:Return To News & Events [1]\n\n08 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Tuesday\, May 8 -- 4:15 to 6 PM\, AC 4th floor hallway\n\nStudent t + eams will present two deliverables: (1) an 'impacts infographic' illustra + ting environmental and/or societal impacts of metals mining and processin + g\, and (2) a microstructure of one of the team's processed alloys\, with + analysis of its structure and properties\n\n.... For More info\n\nJon \n\n + Stolk Jonathan.Stolk@olin.edu\n\n\n[1] http://www.olin.edu/news-events/ [2 + ] http://www.olin.edu/events/feed/20111/event.ics +DTSTART;TZID=America/New_York:20180508T161500 +DTEND;TZID=America/New_York:20180508T161500 +LAST-MODIFIED:20180501T181135Z +SUMMARY:MatSci Final Event +URL;TYPE=URI:http://www.olin.edu/events/matsci-final-event/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20116.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T181640Z +DESCRIPTION:Return To News & Events [1]\n\n09 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Wednesday\, May 9 -- 8 to 9 AM\, Outside AC 128\n\n5 student teams + will present their fully operational amazing kinematic sculptures. This y + ear's theme was 'something in outer space' \n\n.... For More info\n\nDave + \n\n Barrett David.Barrett@olin.edu\n\n\n[1] http://www.olin.edu/news-event + s/ [2] http://www.olin.edu/events/feed/20116/event.ics +DTSTART;TZID=America/New_York:20180509T080000 +DTEND;TZID=America/New_York:20180509T080000 +LAST-MODIFIED:20180501T181640Z +SUMMARY:Final kinematic sculpture presentation +URL;TYPE=URI:http://www.olin.edu/events/final-kinematic-sculpture-presentat + ion/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20121.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T182030Z +DESCRIPTION:Return To News & Events [1]\n\n09 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Wednesday\, May 9 -- 8 to 11 AM\, AC 428\n\nStudents will explain a + nd give demonstrations of\n\n• how a new Olin network architecture will he + lp everyone (even CEOs) understand past\, plan for future and fix present + implementations of communication systems • what research computer networ + k students have been undertaking to understand\, implement\, test and ref + ine the Olin network architecture\n\n.... For More info\n\nAlex \n\n Morrow + Alexander.Morrow@olin.edu\n\n\n[1] http://www.olin.edu/news-events/ [2] ht + tp://www.olin.edu/events/feed/20121/event.ics +DTSTART;TZID=America/New_York:20180509T090000 +DTEND;TZID=America/New_York:20180509T090000 +LAST-MODIFIED:20180501T182030Z +SUMMARY:Computer Networks Class: Past\, Future and Present including Fun Fa + cts +URL;TYPE=URI:http://www.olin.edu/events/computer-networks-class-past-future + -and-present-including-fun-facts/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20126.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T182735Z +DESCRIPTION:Return To News & Events [1]\n\n09 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Wednesday\, May 9 -- 5 to 7 PM\, AC 309 and hallway outside it\n\nP + lease join us to hear Rocket Talks (5-5:45\, AC 309) and Expo (5:45-7\, 3r + d floor hallway). All projects are co-design projects with local communit + y partners\; all are related to design with/for people who are blind or v + isually impaired. Come hear about cane-training games for kids who are bl + ind\; increasing accessibility of flat-screened home appliances\; an auto + mated website audit system for accessibility\; a website with recipes and + cooking tips for people who are blind or visually impaired\; a proposal + to redesign a residential facility for people with visual impairments rel + ated to multiple sclerosis\; and a classroom spinner game for kids at Per + kins School for the Blind.\n\n.... For More info\n\nCaitrin Lynch \n\n and + Paul Ruvolo Caitrin.Lynch@olin.edu\n\n\n[1] http://www.olin.edu/news-even + ts/ [2] http://www.olin.edu/events/feed/20126/event.ics +DTSTART;TZID=America/New_York:20180509T170000 +DTEND;TZID=America/New_York:20180509T170000 +LAST-MODIFIED:20180501T182735Z +SUMMARY:Technology\, Accessibility\, and Design final presentations/demos +URL;TYPE=URI:http://www.olin.edu/events/technology-accessibility-and-design + -final-presentationsdemos/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20136.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T184948Z +DESCRIPTION:Return To News & Events [1]\n\n10 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Thursday\, May 10 -- 12:30 to 2 PM\, AC 213\n\nAll are welcome to s + ee demos of students working on teams and on individual efforts in DREAM. + Each aim to provide resources and opportunities that facilitate differen + t groups empowering themselves to make new things or new narratives aroun + d their identities. Projects range from developing SnapChat filters to ex + pose hidden histories of underrepresented makers in MA to designing curri + cula for fabricating musical instruments in mobile maker spaces (trailers + ) in MS. \n\n.... For More info\n\nAmon Millner \n\n Millner Amon.Millner@o + lin.edu\n\n\n[1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/ + events/feed/20136/event.ics +DTSTART;TZID=America/New_York:20180510T100000 +DTEND;TZID=America/New_York:20180510T100000 +LAST-MODIFIED:20180501T184948Z +SUMMARY:DREAM Final Open House Demos +URL;TYPE=URI:http://www.olin.edu/events/dream-final-open-house-demos/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20131.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T183423Z +DESCRIPTION:Return To News & Events [1]\n\n10 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Thursday\, May 10 -- 12 to 2 PM\, AC 428\n\nStudent teams will pres + ent their final projects involving experimental observations of pattern f + orming systems.\n\n.... For More info\n\nJohn \n\n Geddes John.Geddes@olin. + edu\n\n\n[1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/even + ts/feed/20131/event.ics +DTSTART;TZID=America/New_York:20180510T100000 +DTEND;TZID=America/New_York:20180510T100000 +LAST-MODIFIED:20180501T183423Z +SUMMARY:Nonlinear Science Lab Final Presentations +URL;TYPE=URI:http://www.olin.edu/events/nonlinear-science-lab-final-present + ations/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20141.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180501T190928Z +DESCRIPTION:Return To News & Events [1]\n\n12 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. Saturday\, May 12 -- Wednesday\, May 16\, Library\n\nStudents in th + e Temporary Autonomous Infrastructural Research Group\, an experimental w + orkshop course\, will be displaying materials that they made to communica + te aspects of our shared infrastructural systems to the Olin community.  + \n\n.... For More info\n\nDebbie \n\n Chachra Debbie.Chachra@olin.edu\n\n\n + [1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/events/feed/2 + 0141/event.ics +DTSTART;TZID=America/New_York:20180512T120000 +DTEND;TZID=America/New_York:20180512T120000 +LAST-MODIFIED:20180501T190928Z +SUMMARY:Infrastructure Workshop Course Exhibition +URL;TYPE=URI:http://www.olin.edu/events/infrastructure-workshop-course-exhi + bition/ +END:VEVENT +BEGIN:VEVENT +UID:calendar.20281.field_event_date.0@www.olin.edu +DTSTAMP:20180507T012719Z +CREATED:20180503T184401Z +DESCRIPTION:Return To News & Events [1]\n\n12 May\, 2018 ------------------ + -----------------------------------------------\n\n\nAdd to Outlook [2]\n + \n\n.. *Saturday\, May 12th 2018 from 12pm-4pm on the Firepit Lawn (behind + Residence Halls)*\n\nThis year’s spring carnival will feature a cook-out + of burgers\, hotdogs\, veggie burgers\, and sides\, with snowcones and c + otton candy for dessert. Activities include a caricature artist\, sign-st + op shop [3]\, and airbrush hats and t-shirts. There will also be classic + carnival games such as ring toss\, frog fling\, duck knock down\, and mor + e! Open to Olin students\, faculty\, staff and their friends and families + !\n\n \n\n.... For More info\n\nAshlee \n\n Talbot ashlee.talbot@olin.edu\n + \n\n[1] http://www.olin.edu/news-events/ [2] http://www.olin.edu/events/fe + ed/20281/event.ics [3] http://www.partyvision.com/entertainment-events-ren + tals/sign-shop +DTSTART;TZID=America/New_York:20180512T120000 +DTEND;TZID=America/New_York:20180512T120000 +LAST-MODIFIED:20180503T184401Z +SUMMARY:Olin Carnival 2018 +URL;TYPE=URI:http://www.olin.edu/events/olin-carnival-2018/ +END:VEVENT +END:VCALENDAR diff --git a/tests/test_app.py b/tests/test_app.py index aac55a61..8528a42d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -3,11 +3,7 @@ This test suite requires a local mongodb instance and writes to/drops a database named "abe-unittest" for testing. """ -import os -import unittest - import flask -from pymongo import MongoClient from . import abe_unittest from .context import abe # noqa: F401 @@ -38,7 +34,13 @@ def test_add_sample_events(self): data=flask.json.dumps(event), # use flask.json for datetimes content_type='application/json' ) - self.assertEqual(response._status_code, 201) # check only status code + self.assertEqual(response.status_code, 201) # check only status code + + def test_import_ics(self): + """Imports an ICS feed by URL""" + for feed in sample_data.load_sample_data().icss: + response = self.app.post('/ics/', data=flask.json.dumps(feed), content_type='application/json') + self.assertEqual(response.status_code, 200) def test_add_sample_labels(self): """Adds the sample labels to the database""" @@ -48,26 +50,4 @@ def test_add_sample_labels(self): data=flask.json.dumps(event), content_type='application/json' ) - self.assertEqual(response._status_code, 201) - - def test_date_range(self): - from abe import database as db - sample_data.load_data(db) - - with self.subTest("a six-month query returns some events"): - response = self.app.get('/events/?start=2017-01-01&end=2017-07-01') - self.assertEqual(response._status_code, 200) - self.assertEqual(len(flask.json.loads(response.data)), 25) - - with self.subTest("a one-year query returns all events"): - response = self.app.get('/events/?start=2017-01-01&end=2018-01-01') - self.assertEqual(response._status_code, 200) - self.assertEqual(len(flask.json.loads(response.data)), 69) - - with self.subTest("a two-year query is too long"): - response = self.app.get('/events/?start=2017-01-01&end=2019-01-01') - self.assertEqual(response._status_code, 404) - - with self.subTest("a one-year query works for leap years"): - response = self.app.get('/events/?start=2020-01-01&end=2021-01-01') - self.assertEqual(response._status_code, 200) + self.assertEqual(response.status_code, 201) diff --git a/tests/test_auth.py b/tests/test_auth.py index f319251a..4b7cd447 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -3,19 +3,18 @@ """ import os import unittest -import sys from importlib import reload import flask -from flask import request from werkzeug.exceptions import HTTPException -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -from abe import auth +from abe import auth # noqa: F401 app = flask.Flask(__name__) + class AuthTestCase(unittest.TestCase): + def test_intranet_ips(self): global auth os.environ['INTRANET_IPS'] = '127.0.0.1/24' @@ -76,5 +75,23 @@ def route(): with self.assertRaises(HTTPException) as http_error: route() -if __name__ == '__main__': - unittest.main() + # Test auth cookie + os.environ['SHARED_SECRET'] = 'security' + auth = reload(auth) + with self.subTest("off-whitelist IP, no cookie"): + with app.test_request_context('/', environ_base={'REMOTE_ADDR': '127.0.1.1'}): + with self.assertRaises(HTTPException) as http_error: + route() + self.assertEqual(http_error.exception.code, 401) + + with self.subTest("off-whitelist IP, correct cookie"): + with app.test_request_context('/', headers={"COOKIE": "app_secret=security"}, + environ_base={'REMOTE_ADDR': '127.0.1.1'}): + assert route() == 'ok' + + with self.subTest("off-whitelist IP, incorrect cookie"): + with app.test_request_context('/', headers={"COOKIE": "app_secret=obscurity"}, + environ_base={'REMOTE_ADDR': '127.0.1.1'}): + with self.assertRaises(HTTPException) as http_error: + route() + self.assertEqual(http_error.exception.code, 401) diff --git a/tests/test_email_helpers.py b/tests/test_email_helpers.py index 85a9f20a..15643a7c 100644 --- a/tests/test_email_helpers.py +++ b/tests/test_email_helpers.py @@ -1,6 +1,6 @@ -from unittest import skip -from unittest.mock import Mock, patch, ANY import email +from unittest.mock import Mock, patch + from icalendar import Calendar from . import abe_unittest @@ -15,8 +15,12 @@ with open('./tests/cal_script.txt', 'r') as cal_file: cal = Calendar.from_ical(cal_file.read()) -class EmailHelpersTestCase(abe_unittest.TestCase): +serv = Mock() +serv.send_message = Mock() +serv.close = Mock() + +class EmailHelpersTestCase(abe_unittest.TestCase): def test_get_msg_list(self): message_txt = [s.encode() for s in ['first line', 'second line']] @@ -28,14 +32,12 @@ def test_get_msg_list(self): self.assertEqual(len(messages), 1) self.assertEqual(messages[0].as_string(), "\nfirst line\nsecond line") - def test_ical_to_dict(self): test_dict, test_sender = email_helpers.ical_to_dict(cal) self.assertEqual(test_sender, 'test.case@tests.com') self.assertEqual(test_dict['description'], 'Test Event') self.assertEqual(test_dict['labels'], ['test']) - def test_get_messages_from_email(self): with patch('poplib.POP3_SSL') as pop_conn_factory: pop_conn = pop_conn_factory() @@ -48,13 +50,11 @@ def test_get_messages_from_email(self): pop_conn.quit.assert_called() self.assertEqual(len(messages), 1) - def test_get_calendars_from_messages(self): cal_list = email_helpers.get_calendars_from_messages([message]) self.assertEqual(len(cal_list), 1) self.assertIsInstance(cal_list[0], Calendar) - @patch('abe.helper_functions.email_helpers.error_reply', return_value=None) @patch('abe.helper_functions.email_helpers.reply_email', return_value=None) def test_cal_to_event(self, email_error, email_reply): @@ -65,7 +65,6 @@ def test_cal_to_event(self, email_error, email_reply): self.assertEqual(exit_code, 201) self.assertEqual(event_dict['description'], event['description']) - def test_smtp_connect(self): with patch('smtplib.SMTP_SSL') as server_factory: server = server_factory() @@ -75,42 +74,33 @@ def test_smtp_connect(self): server.ehlo.assert_called() server.login.assert_called() - @patch('abe.helper_functions.email_helpers.send_email', return_value=None) - @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=('server','from_addr')) - def test_error_reply(self, smtp, send): + @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=(serv, 'from_addr')) + def test_error_reply(self, smtp): error = Mock() error.errors = [12] error.message = "This is an error message" to = "to_addr" email_helpers.error_reply(to, error) smtp.assert_called() - send.assert_called_with('server', ANY, 'from_addr', to) - - - @patch('abe.helper_functions.email_helpers.send_email', return_value=None) - @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=('server','from_addr')) - def test_reply_email(self, smtp, send): - event_dict = {'title':'Test', 'labels':['test'], 'description':'empty test'} + serv.send_message.assert_called() + serv.close.assert_called() + + @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=(serv, 'from_addr')) + def test_reply_email(self, smtp): + event_dict = {'title': 'Test', + 'start': '2018-04-30 14:51:24', + 'end': '2018-04-30 14:51:24', + 'labels': ['test'], + 'description': 'empty test', + 'id': 'id_string'} to = "to_addr" email_helpers.reply_email(to, event_dict) smtp.assert_called() - send.assert_called_with('server', ANY, 'from_addr', to) - - - def test_send_email(self): - server = Mock() - server.sendmail = Mock() - server.close = Mock() - email_text = 'email text' - sent_from = 'from address' - to = 'to address' - email_helpers.send_email(server, email_text, sent_from, to) - server.sendmail.assert_called_once_with(sent_from, to, email_text.encode('utf-8')) - server.close.assert_called_once() - - @patch('abe.helper_functions.email_helpers.send_email', return_value=None) - @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=('server', 'from_addr')) + serv.send_message.assert_called() + serv.close.assert_called() + + @patch('abe.helper_functions.email_helpers.smtp_connect', return_value=(serv, 'from_addr')) @patch('abe.helper_functions.email_helpers.get_messages_from_email', return_value=[message]) - def test_scrape(self, msgs_from_email, smtp, send): + def test_scrape(self, msgs_from_email, smtp): completed = email_helpers.scrape() self.assertEqual(len(completed), 1) diff --git a/tests/test_events.py b/tests/test_events.py new file mode 100644 index 00000000..b3c5d4f9 --- /dev/null +++ b/tests/test_events.py @@ -0,0 +1,129 @@ +from unittest import skip + +import flask +import isodate + +from . import abe_unittest +from .context import abe # noqa: F401 + +# These imports have to happen after .context sets the environment variables +import abe.app # isort:skip +from abe import sample_data # isort:skip + + +class EventsTestCase(abe_unittest.TestCase): + + def setUp(self): + super().setUp() + self.app = abe.app.app.test_client() + sample_data.load_data(self.db) + + def test_get_events(self): + with self.subTest("a six-month query returns some events"): + response = self.app.get('/events/?start=2017-01-01&end=2017-07-01') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(flask.json.loads(response.data)), 25) + + with self.subTest("a one-year query returns all events"): + response = self.app.get('/events/?start=2017-01-01&end=2018-01-01') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(flask.json.loads(response.data)), 69) + + with self.subTest("a two-year query is too long"): + response = self.app.get('/events/?start=2017-01-01&end=2019-01-01') + self.assertEqual(response.status_code, 404) + + with self.subTest("a one-year query works for leap years"): + response = self.app.get('/events/?start=2020-01-01&end=2021-01-01') + self.assertEqual(response.status_code, 200) + + with self.subTest("an unauthenticated sender retrieves only public events"): + event_response = self.app.get('/events/?start=2017-01-01&end=2017-07-01') + # TODO: change the following when #75 is implemented + self.assertEqual(len(flask.json.loads(event_response.data)), 25) + + def test_post(self): + event = { + 'title': 'test_post', + 'start': isodate.parse_datetime('2018-05-04T09:00:00') + } + + with self.subTest("succeeds when required fields are present"): + response = self.app.post( + '/events/', + data=flask.json.dumps(event), + content_type='application/json' + ) + self.assertEqual(response.status_code, 201) + + with self.subTest("fails when fields are missing"): + evt = event.copy() + del evt['title'] + response = self.app.post( + '/events/', + data=flask.json.dumps(evt), + content_type='application/json' + ) + self.assertEqual(response.status_code, 400) + self.assertRegex(flask.json.loads(response.data)['error_message'], r"^ValidationError.*'title'") + + evt = event.copy() + del evt['start'] + response = self.app.post( + '/events/', + data=flask.json.dumps(evt), + content_type='application/json' + ) + self.assertEqual(response.status_code, 400) + self.assertRegex(flask.json.loads(response.data)['error_message'], r"^ValidationError.*'start'") + + def test_post_auth(self): + event = { + 'title': 'test_post', + 'start': isodate.parse_datetime('2018-05-04T09:00:00') + } + with self.subTest("fails when the client is not yet authorized"): + response = self.app.post( + '/events/', + data=flask.json.dumps(event), + content_type='application/json', + headers={ + 'X-Forwarded-For': '192.168.1.1', + } + ) + self.assertEqual(response.status_code, 401) + + with self.subTest("succeeds when required fields are present"): + response = self.app.post( + '/events/', + data=flask.json.dumps(event), + content_type='application/json' + ) + self.assertEqual(response.status_code, 201) + + with self.subTest("succeeds due to auth cookie"): + response = self.app.post( + '/events/', + data=flask.json.dumps(event), + content_type='application/json', + headers={ + 'X-Forwarded-For': '192.168.1.1', + } + ) + self.assertEqual(response.status_code, 201) + + @skip("Unimplemented test") + def test_put(self): + # TODO: test success + # TODO: test invalid id + # TODO: test invalid data + # TODO: test unauthorized user + pass + + @skip("Unimplemented test") + def test_delete(self): + # TODO: test success + # TODO: test invalid id + # TODO: test invalid data + # TODO: test unauthorized user + pass diff --git a/tests/test_ics_helpers.py b/tests/test_ics_helpers.py index 69e999b1..8a2aa33d 100644 --- a/tests/test_ics_helpers.py +++ b/tests/test_ics_helpers.py @@ -2,7 +2,9 @@ from unittest import skip import icalendar +from icalendar import Calendar +from abe import sample_data from . import abe_unittest from .context import abe # noqa: F401 @@ -104,10 +106,9 @@ def test_ics_to_dict(self): pass # TODO: ics_helpers.ics_to_dict(component, labels, ics_id=None) - @skip('Unimplemented test') def test_extract_ics(self): - pass - # TODO: ics_helpers.extract_ics(cal, ics_url, labels=None) + cal = Calendar.from_ical(sample_data.load_sample_data().ics_data) + ics_helpers.extract_ics(cal, '') @skip('Unimplemented test') def test_update_ics_to_mongo(self): diff --git a/tests/test_labels.py b/tests/test_labels.py new file mode 100644 index 00000000..944ec84f --- /dev/null +++ b/tests/test_labels.py @@ -0,0 +1,118 @@ +import flask + +from . import abe_unittest +from .context import abe # noqa: F401 + +# These imports have to happen after .context sets the environment variables +import abe.app # isort:skip +from abe import sample_data # isort:skip + + +class LabelsTestCase(abe_unittest.TestCase): + + def setUp(self): + super().setUp() + self.app = abe.app.app.test_client() + sample_data.load_data(self.db) + + def test_get(self): + response = self.app.get('/labels/') + self.assertEqual(response.status_code, 200) + labels = flask.json.loads(response.data) + self.assertEqual(len(labels), 15) + label = next((l for l in labels if l['name'] == 'library'), None) + self.assertIsNotNone(label) + self.assertEqual(label['name'], 'library') + self.assertEqual(label['color'], '#26AAA5') + self.assertRegex(label['description'], r'relating to the Olin Library') + self.assertEqual(label['url'], 'http://library.olin.edu/') + + def test_post(self): + label1 = { + 'name': 'label-test', + } + with self.subTest("succeeds when required fields are present"): + response = self.app.post( + '/labels/', + data=flask.json.dumps(label1), + content_type='application/json' + ) + self.assertEqual(response.status_code, 201) + + # FIXME: + # with self.subTest("fails on a duplicate label name"): + # response = self.app.post( + # '/labels/', + # data=flask.json.dumps(label1), + # content_type='application/json' + # ) + # self.assertEqual(response.status_code, 400) + + with self.subTest("fails when fields are missing"): + response = self.app.post( + '/labels/', + data=flask.json.dumps({}), + content_type='application/json' + ) + self.assertEqual(response.status_code, 400) + self.assertRegex(flask.json.loads(response.data)['error_message'], r"^ValidationError.*'name'") + + def test_post_auth(self): + label_noauth = { + 'name': 'label-test-notauth', + } + label_success = { + 'name': 'label-test-success', + } + label_auth = { + 'name': 'label-test-auth', + } + with self.subTest("fails when the client is not yet authorized"): + response = self.app.post( + '/labels/', + data=flask.json.dumps(label_noauth), + content_type='application/json', + headers={'X-Forwarded-For': '192.168.1.1'} + ) + self.assertEqual(response.status_code, 401) + + with self.subTest("succeeds when required fields are present"): + response = self.app.post( + '/labels/', + data=flask.json.dumps(label_success), + content_type='application/json' + ) + self.assertEqual(response.status_code, 201) + + with self.subTest("succeededs with auth cookie"): + response = self.app.post( + '/labels/', + data=flask.json.dumps(label_auth), + content_type='application/json', + headers={'X-Forwarded-For': '192.168.1.1'} + ) + self.assertEqual(response.status_code, 201) + + def test_put(self): + # TODO: test success + # TODO: test invalid id + # TODO: test invalid data + with self.subTest("fails when the client is not authorized"): + response = self.app.put( + '/labels/library', + data=flask.json.dumps({'description': 'new description'}), + content_type='application/json', + headers={'X-Forwarded-For': '192.168.1.1'} + ) + self.assertEqual(response.status_code, 401) + + def test_delete(self): + # TODO: test success + # TODO: test invalid id + # TODO: test invalid data + with self.subTest("fails when the client is not authorized"): + response = self.app.delete( + '/labels/library', + headers={'X-Forwarded-For': '192.168.1.1'} + ) + self.assertEqual(response.status_code, 401) diff --git a/tests/test_recurrences.py b/tests/test_recurrences.py index 63db6af2..deb46447 100644 --- a/tests/test_recurrences.py +++ b/tests/test_recurrences.py @@ -1,11 +1,12 @@ from datetime import datetime +from abe.helper_functions import recurring_helpers + from . import abe_unittest from .context import abe # noqa: F401 # This import has to happen after .context sets the environment variables from abe.helper_functions import sub_event_helpers # isort:skip -from abe.helper_functions import recurring_helpers # TODO: add test cases for YEARLY frequency (are there others)? # TODO: add test cases for by_month, by_month_day diff --git a/tests/test_sample_data.py b/tests/test_sample_data.py index 9782dc2c..e126cf30 100644 --- a/tests/test_sample_data.py +++ b/tests/test_sample_data.py @@ -3,8 +3,6 @@ This test suite requires a local mongodb instance and writes to/drops a database named "abe-unittest" for testing. """ -import unittest - from . import abe_unittest from .context import abe # noqa: F401 diff --git a/tests/test_subscription_resources.py b/tests/test_subscription_resources.py index 2c2d14c1..637e58de 100644 --- a/tests/test_subscription_resources.py +++ b/tests/test_subscription_resources.py @@ -1,13 +1,9 @@ -from datetime import datetime from unittest import skip -import icalendar - from . import abe_unittest from .context import abe # noqa: F401 # This import has to happen after .context sets the environment variables -# from abe.document_model import subscription_documents # isort:skip from abe.resource_models import subscription_resources # isort:skip