diff --git a/.env b/.env index c2b9129..f0b3e85 100644 --- a/.env +++ b/.env @@ -19,6 +19,9 @@ EXPOSED_SERVER_TYPE_PROD=http EXPOSED_SERVER_NAME_PROD=annotation EXPOSED_PORT_PROD=80 +# This key must be set to a string of your choice to have a secure deployment. +BACKEND_SECRET_KEY= + AUTH_MODULE=eve #MONGO_URI= #VEGAS_CLIENT_SECRET= diff --git a/README.md b/README.md index 84e3e0f..79aa3fd 100644 --- a/README.md +++ b/README.md @@ -93,21 +93,25 @@ See [Docker Environments](#docker-environments). ### Getting started with the "eve" auth module 1. Set `AUTH_MODULE=eve` in the `.env` file. -2. On first setup, the database will have no users in it. To add users, either import the testing +2. Set the `BACKEND_SECRET_KEY` variable in the `.env` file. If this is not done, the default + testing value will be used which is NOT secure as it is checked in to this repo. +3. On first setup, the database will have no users in it. To add users, either import the testing dataset, or use the scripts in `scripts/data` in the backend to manage users. (See the *User management using "eve" auth module* sections in [Development Environment](#development-environment) and [Docker Environments](#docker-environments) below for exact commands.) Once an admin user has been added, users can also be added/managed by logging in as that user and using the Admin Dashboad. -3. Log in with the user credentials. +4. Log in with the user credentials. ### Getting started with the "vegas" auth module 1. Set `AUTH_MODULE=vegas` and `VEGAS_CLIENT_SECRET` in the `.env` file. `VEGAS_CLIENT_SECRET` must be obtained by contacting one of the PINE developers and being authorized to use it. -2. VEGAS users are managed externally. -3. Log in with your JHED credentials. +2. Set the `BACKEND_SECRET_KEY` variable in the `.env` file. If this is not done, the default + testing value will be used which is NOT secure as it is checked in to this repo. +3. VEGAS users are managed externally. +4. Log in with your JHED credentials. ---------------------------------------------------------------------------------------------------- @@ -154,8 +158,11 @@ Once the dev stack is up and running, the following ports are accessible: The backend API is documented using an [OpenAPI specification](https://swagger.io/specification/). This specification covers the main REST API used by PINE. A copy of the -[Swagger UI](https://swagger.io/tools/swagger-ui/) is hosted at `http[s]:///api/ui` and -the specification itself is hosted at `http[s]:///api/openapi.yaml`. +[Swagger UI](https://swagger.io/tools/swagger-ui/) is hosted at `http[s]:///swagger` and +the specification itself is hosted at `http[s]:///openapi.yaml`. The easiest way to +use the Swagger UI is to log in to PINE via the normal web UI and then open the Swagger UI. This +allows the browser to set the user session cookie based on your logged in credentials and then that +cookie will be used in all calls to the Swagger UI/API. This specification is found in the source code at `backend/pine/backend/api/openapi.yaml`. *NOTE* however that this file is autogenerated by the `./update_openapi.sh` script. The "base" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d6a7357..ce97f1e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -121,6 +121,7 @@ stages: VEGAS_SERVER: https://slife.jh.edu VEGAS_CLIENT_ID: 1976d9d4-be86-44ce-aa0f-c5a4b295c701 VEGAS_CLIENT_SECRET: $(vegas-client-secret-dev) + BACKEND_SECRET_KEY: $(backend-secret-key-dev) eve: MONGO_URI: $(mongo-uri-dev) azure-secret: @@ -149,6 +150,7 @@ stages: VEGAS_SERVER: https://my.jh.edu VEGAS_CLIENT_ID: b7590f07-cbd9-48b1-82f4-8aab02470831 VEGAS_CLIENT_SECRET: $(vegas-client-secret-prod) + BACKEND_SECRET_KEY: $(backend-secret-key-prod) eve: MONGO_URI: $(mongo-uri-prod) azure-secret: diff --git a/backend/Pipfile b/backend/Pipfile index 204b37e..9156945 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -24,6 +24,7 @@ matplotlib = "~=3.4.2" scipy = "~=1.7.1" tabulate = "~=0.8.9" multiprocessing-logging = "~=0.3.1" +flask-httpauth = "~=4.4.0" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 53468a9..769cb57 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "45a7e976255520c4f430a83e1eb1fa4f6b4a0133332812a01a92702c594b8970" + "sha256": "3a30a022ccd4fbe028c8cf2c8f741b9c7f7fa72e039dba391da62a20e58c5273" }, "pipfile-spec": 6, "requires": { @@ -137,6 +137,8 @@ "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586", + "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3", "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" @@ -167,6 +169,14 @@ "index": "pypi", "version": "==3.0.10" }, + "flask-httpauth": { + "hashes": [ + "sha256:bcaaa7a35a3cba0b2eafd4f113b3016bf70eb78087456d96484c3c18928b813a", + "sha256:d9131122cdc5709dda63790f6e9b3142d8101447d424b0b95ffd4ee279f49539" + ], + "index": "pypi", + "version": "==4.4.0" + }, "idna": { "hashes": [ "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", @@ -236,30 +246,50 @@ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", + "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", + "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", + "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", + "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", + "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -271,28 +301,30 @@ }, "matplotlib": { "hashes": [ - "sha256:0bea5ec5c28d49020e5d7923c2725b837e60bc8be99d3164af410eb4b4c827da", - "sha256:1c1779f7ab7d8bdb7d4c605e6ffaa0614b3e80f1e3c8ccf7b9269a22dbc5986b", - "sha256:21b31057bbc5e75b08e70a43cefc4c0b2c2f1b1a850f4a0f7af044eb4163086c", - "sha256:32fa638cc10886885d1ca3d409d4473d6a22f7ceecd11322150961a70fab66dd", - "sha256:3a5c18dbd2c7c366da26a4ad1462fe3e03a577b39e3b503bbcf482b9cdac093c", - "sha256:5826f56055b9b1c80fef82e326097e34dc4af8c7249226b7dd63095a686177d1", - "sha256:6382bc6e2d7e481bcd977eb131c31dee96e0fb4f9177d15ec6fb976d3b9ace1a", - "sha256:6475d0209024a77f869163ec3657c47fed35d9b6ed8bccba8aa0f0099fbbdaa8", - "sha256:6a6a44f27aabe720ec4fd485061e8a35784c2b9ffa6363ad546316dfc9cea04e", - "sha256:7a58f3d8fe8fac3be522c79d921c9b86e090a59637cb88e3bc51298d7a2c862a", - "sha256:7ad19f3fb6145b9eb41c08e7cbb9f8e10b91291396bee21e9ce761bb78df63ec", - "sha256:85f191bb03cb1a7b04b5c2cca4792bef94df06ef473bc49e2818105671766fee", - "sha256:956c8849b134b4a343598305a3ca1bdd3094f01f5efc8afccdebeffe6b315247", - "sha256:a9d8cb5329df13e0cdaa14b3b43f47b5e593ec637f13f14db75bb16e46178b05", - "sha256:b1d5a2cedf5de05567c441b3a8c2651fbde56df08b82640e7f06c8cd91e201f6", - "sha256:b26535b9de85326e6958cdef720ecd10bcf74a3f4371bf9a7e5b2e659c17e153", - "sha256:c541ee5a3287efe066bbe358320853cf4916bc14c00c38f8f3d8d75275a405a9", - "sha256:d8d994cefdff9aaba45166eb3de4f5211adb4accac85cbf97137e98f26ea0219", - "sha256:df815378a754a7edd4559f8c51fc7064f779a74013644a7f5ac7a0c31f875866" + "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda", + "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684", + "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5", + "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919", + "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f", + "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913", + "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838", + "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235", + "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea", + "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3", + "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506", + "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330", + "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a", + "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b", + "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617", + "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5", + "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40", + "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa", + "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd", + "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318", + "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da" ], "index": "pypi", - "version": "==3.4.2" + "version": "==3.4.3" }, "multiprocessing-logging": { "hashes": [ @@ -311,37 +343,39 @@ }, "numpy": { "hashes": [ - "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33", - "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5", - "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1", - "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1", - "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac", - "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4", - "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50", - "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6", - "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267", - "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172", - "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af", - "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8", - "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2", - "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63", - "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1", - "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8", - "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16", - "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214", - "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd", - "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68", - "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062", - "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e", - "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f", - "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b", - "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd", - "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671", - "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a", - "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a" + "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf", + "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2", + "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6", + "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad", + "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc", + "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254", + "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0", + "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218", + "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13", + "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef", + "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737", + "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723", + "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3", + "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1", + "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d", + "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52", + "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3", + "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5", + "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f", + "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496", + "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f", + "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724", + "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931", + "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f", + "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7", + "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38", + "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557", + "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57", + "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310", + "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419" ], - "markers": "python_version >= '3.7'", - "version": "==1.21.1" + "markers": "python_version < '3.11' and python_version >= '3.7'", + "version": "==1.21.2" }, "overrides": { "hashes": [ diff --git a/backend/pine/backend/admin/openapi.yaml b/backend/pine/backend/admin/openapi.yaml index ef986c5..aecc51e 100644 --- a/backend/pine/backend/admin/openapi.yaml +++ b/backend/pine/backend/admin/openapi.yaml @@ -4,6 +4,7 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] tags: - name: admin diff --git a/backend/pine/backend/annotations/openapi.yaml b/backend/pine/backend/annotations/openapi.yaml index f2eb309..6247a2b 100644 --- a/backend/pine/backend/annotations/openapi.yaml +++ b/backend/pine/backend/annotations/openapi.yaml @@ -4,6 +4,8 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - name: annotations diff --git a/backend/pine/backend/api/base.yaml b/backend/pine/backend/api/base.yaml index 223187e..b156951 100644 --- a/backend/pine/backend/api/base.yaml +++ b/backend/pine/backend/api/base.yaml @@ -21,9 +21,8 @@ info: url: https://github.com/JHUAPL/PINE/blob/master/LICENSE servers: - - url: http://localhost:5000 - - url: https://localhost:8888/api - - url: https://dev-nlpannotator.pm.jh.edu/api + - url: /api + description: This server. paths: {} @@ -31,4 +30,8 @@ components: securitySchemes: cookieAuth: $ref: "./components.yaml#/securitySchemes/cookieAuth" + eveBasicAuth: + $ref: "./components.yaml#/securitySchemes/eveBasicAuth" + vegasBearerAuth: + $ref: "./components.yaml#/securitySchemes/vegasBearerAuth" schemas: {} diff --git a/backend/pine/backend/api/components.yaml b/backend/pine/backend/api/components.yaml index f52c6ce..1295d09 100644 --- a/backend/pine/backend/api/components.yaml +++ b/backend/pine/backend/api/components.yaml @@ -4,13 +4,46 @@ securitySchemes: cookieAuth: description: | + This is the basic authentication mechanism for the PINE backend, used by all auth modules. + This an example command to provision and print the session key using eve: `curl -X POST -H "Content-Type:application/json" -d '{"username":"ada@pine.jhuapl.edu","password":"ada@pine.jhuapl.edu"}' http://localhost:5000/auth/login --cookie-jar - --output /dev/null --silent | grep -o -P "session\s.+" | sed -e 's/session\s/session=/' -` + + The easiest way to use this auth using the Swagger UI is to log in using the normal PINE web + UI, which will set the cookie in the browser. Then the Swagger UI will use the same cookie + for all its calls. type: apiKey in: cookie name: session + eveBasicAuth: + description: | + If the eve auth module is used, you can use basic authentication via a header and the + cookie/session will be created using that login information. This is basically the same as + logging in before each call. It is less efficient than using the cookie auth but also does + not rely on cookies or session state which can be difficult to use outside a browser. + type: http + scheme: basic + + vegasBearerAuth: + description: | + If the vegas auth module is used, you can get a VEGAS access token outside of PINE and set it + as an auth bearer header and the cookie/session will be created using that login information. + This is basically the same as authenticating your token before each call. It is less + efficient than using the cookie auth but also does not rely on cookies or session state which + can be difficult to use outside a browser. + + An example call to get a VEGAS token might look like: + `curl -H "Authentication: hmac " -H "Date: 1629328547884" + -d "grant_type=client_credentials" -X POST https://slife.jh.edu/VEGAS/api/oauth2/accesstoken`. + This will return a JSON object of the form `{"access_token": "...", "refresh_token": "...", + "token_type":"bearer","issued_at":1629328554626,"expires_in":1800}`. Grab the `access_token` + string and use it as an auth bearer header. + type: http + scheme: bearer + bearerFormat: JWT + parameters: userIdParam: diff --git a/backend/pine/backend/api/openapi.yaml b/backend/pine/backend/api/openapi.yaml index 78247a2..070d241 100644 --- a/backend/pine/backend/api/openapi.yaml +++ b/backend/pine/backend/api/openapi.yaml @@ -25,9 +25,8 @@ info: name: AGPL-3.0 url: 'https://github.com/JHUAPL/PINE/blob/master/LICENSE' servers: - - url: 'http://localhost:5000' - - url: 'https://localhost:8888/api' - - url: 'https://dev-nlpannotator.pm.jh.edu/api' + - url: /api + description: This server. paths: /admin/users: get: @@ -93,6 +92,7 @@ paths: example: Error message from the server. security: - cookieAuth: [] + - eveBasicAuth: [] post: summary: Create New User description: > @@ -215,6 +215,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] '/admin/users/{user_id}': get: summary: Get User Details @@ -258,6 +259,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] put: summary: Update user details. description: > @@ -350,6 +352,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] delete: summary: Delete User description: > @@ -382,6 +385,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] '/admin/users/{user_id}/password': put: summary: Update User Password @@ -438,6 +442,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] /admin/system/export: get: summary: Export Database @@ -469,6 +474,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] /admin/system/import: put: summary: Import Database (Update) @@ -537,6 +543,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] post: summary: Import Database (Replace) description: > @@ -598,6 +605,7 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] '/annotations/mine/by_document_id/{doc_id}': get: summary: Get My Document Annotations @@ -698,6 +706,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] post: summary: Save My Annotations description: > @@ -794,6 +804,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/annotations/mine/by_collection_id/{collection_id}': post: summary: Set Collection Annotations @@ -909,6 +921,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/annotations/others/by_document_id/{doc_id}': get: summary: Get Others' Document Annotations @@ -949,6 +963,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/annotations/by_document_id/{doc_id}': get: summary: Get All Document Annotations @@ -989,6 +1005,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /auth/module: get: summary: Get Auth Module @@ -1072,7 +1090,18 @@ paths: Example: `curl -X GET http://localhost:5000/auth/logged_in_user --cookie session.cookie` + + + Note that this operation does not REQUIRE the user to be logged in, but + it is marked as + + requiring security so that the Swagger UI will send in whatever you have + configured. operationId: auth_logged_in_user + security: + - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - auth responses: @@ -1114,6 +1143,8 @@ paths: operationId: auth_user_details security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - auth responses: @@ -1148,6 +1179,7 @@ paths: operationId: auth_eve_update_user_details security: - cookieAuth: [] + - eveBasicAuth: [] tags: - auth_eve requestBody: @@ -1239,6 +1271,8 @@ paths: - auth security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] responses: '200': description: Successfully logged out. @@ -1325,6 +1359,7 @@ paths: - auth_eve security: - cookieAuth: [] + - eveBasicAuth: [] responses: '200': description: Returns all users. @@ -1353,6 +1388,7 @@ paths: - auth_eve security: - cookieAuth: [] + - eveBasicAuth: [] requestBody: content: application/json: @@ -1442,10 +1478,11 @@ paths: description: An auth token obtained by Vegas out-of-band from PINE. type: object properties: &ref_48 - auth_token: + access_token: type: string token_type: type: string + example: bearer additionalProperties: &ref_49 {} responses: '200': @@ -1523,6 +1560,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/unarchived/{page}': get: summary: Get Paginated Unarchived Collections @@ -1565,6 +1604,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /collections/archived: get: summary: Get Archived Collections @@ -1595,6 +1636,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/archived/{page}': get: summary: Get Paginated Archived Collections @@ -1637,6 +1680,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/archive/{collection_id}': put: summary: Archive Collection @@ -1677,6 +1722,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/unarchive/{collection_id}': put: summary: Unarchive Collection @@ -1717,6 +1764,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/by_id/{collection_id}': get: summary: Get Collection @@ -1757,6 +1806,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/by_id/{collection_id}/download': get: summary: Download Collection Data @@ -1797,6 +1848,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/add_annotator/{collection_id}': post: summary: Add Collection Annotator @@ -1883,6 +1936,8 @@ paths: example: Error message from the server. security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/add_viewer/{collection_id}': post: summary: Add Collection Viewer @@ -1970,6 +2025,8 @@ paths: example: Error message from the server. security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/add_label/{collection_id}': post: summary: Add Collection Label @@ -2055,6 +2112,8 @@ paths: example: Error message from the server. security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /collections: post: summary: Create Collection @@ -2276,6 +2335,8 @@ paths: example: Error message from the server. security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/static_images/{collection_id}': get: summary: Get Collection Static Images @@ -2319,6 +2380,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/images/{collection_id}': get: summary: Get Collection Images @@ -2360,6 +2423,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/image_exists/{collection_id}/{path}': get: summary: Check Collection Image @@ -2408,6 +2473,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/image/{collection_id}/{path}': get: summary: Get Collection Image @@ -2457,6 +2524,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] post: summary: Upload Collection Image description: > @@ -2533,6 +2602,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/collections/user_permissions/{collection_id}': get: summary: Get Collection User Permissions @@ -2591,6 +2662,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/by_id/{doc_id}': get: summary: Get Document @@ -2649,6 +2722,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] delete: summary: Delete Document description: > @@ -2732,6 +2807,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /documents/by_ids: delete: summary: Delete Documents @@ -2787,6 +2864,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/count_by_collection_id/{collection_id}': get: summary: Get Collection Document Count @@ -2828,6 +2907,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/by_collection_id_all/{collection_id}': get: summary: Get All Collection Documents @@ -2873,6 +2954,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/by_collection_id_paginated/{collection_id}': get: summary: Get Paginated Collection Documents @@ -2933,6 +3016,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/user_permissions/{doc_id}': get: summary: Get User Document Permissions @@ -2973,6 +3058,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/documents/metadata/{doc_id}': put: summary: Update Document Metadata @@ -3031,6 +3118,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /documents: post: summary: Create Document @@ -3110,6 +3199,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/iaa_reports/by_collection_id/{collection_id}': get: summary: Get IAA Report for Collection @@ -3242,6 +3333,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] post: summary: Create IAA Report for Collection description: > @@ -3286,6 +3379,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /pipelines: get: summary: Get Pipelines @@ -3386,6 +3481,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/by_id/{pipeline_id}': get: summary: Get Pipeline @@ -3431,6 +3528,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/classifiers/by_collection_id/{collection_id}': get: summary: Get Collection Classifier @@ -3499,6 +3598,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] /pipelines/metrics: get: summary: Get Collection Metrics @@ -3572,6 +3673,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/metrics/by_classifier_id/{classifier_id}': get: summary: Get Classifier Metrics @@ -3626,6 +3729,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/next_document/by_classifier_id/{classifier_id}': get: summary: Get Next Document to Annotate @@ -3669,6 +3774,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/next_document/by_classifier_id/{classifier_id}/{doc_id}': post: summary: Advance Next Document @@ -3723,6 +3830,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/status/{pipeline_id}': get: summary: Get Pipeline Status @@ -3833,6 +3942,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/classifiers/status/{classifier_id}': get: summary: Get Classifier Status @@ -3872,6 +3983,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/running_jobs/{classifier_id}': get: summary: Get Classifier Running Jobs @@ -3913,6 +4026,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/job_results/{classifier_id}/{job_id}': get: summary: Get Classifier Job Results @@ -3981,6 +4096,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/train/{classifier_id}': post: summary: Train Classifier @@ -4072,6 +4189,8 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] '/pipelines/predict/{classifier_id}': post: summary: Predict Using Classifier @@ -4230,10 +4349,16 @@ paths: content: *ref_1 security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] components: securitySchemes: cookieAuth: description: > + This is the basic authentication mechanism for the PINE backend, used by + all auth modules. + + This an example command to provision and print the session key using eve: @@ -4242,9 +4367,68 @@ components: '{"username":"ada@pine.jhuapl.edu","password":"ada@pine.jhuapl.edu"}' http://localhost:5000/auth/login --cookie-jar - --output /dev/null --silent | grep -o -P "session\s.+" | sed -e 's/session\s/session=/' -` + + + The easiest way to use this auth using the Swagger UI is to log in using + the normal PINE web + + UI, which will set the cookie in the browser. Then the Swagger UI will + use the same cookie + + for all its calls. type: apiKey in: cookie name: session + eveBasicAuth: + description: > + If the eve auth module is used, you can use basic authentication via a + header and the + + cookie/session will be created using that login information. This is + basically the same as + + logging in before each call. It is less efficient than using the cookie + auth but also does + + not rely on cookies or session state which can be difficult to use + outside a browser. + type: http + scheme: basic + vegasBearerAuth: + description: > + If the vegas auth module is used, you can get a VEGAS access token + outside of PINE and set it + + as an auth bearer header and the cookie/session will be created using + that login information. + + This is basically the same as authenticating your token before each + call. It is less + + efficient than using the cookie auth but also does not rely on cookies + or session state which + + can be difficult to use outside a browser. + + + An example call to get a VEGAS token might look like: + + `curl -H "Authentication: hmac " -H "Date: + 1629328547884" + + -d "grant_type=client_credentials" -X POST + https://slife.jh.edu/VEGAS/api/oauth2/accesstoken`. + + This will return a JSON object of the form `{"access_token": "...", + "refresh_token": "...", + + "token_type":"bearer","issued_at":1629328554626,"expires_in":1800}`. + Grab the `access_token` + + string and use it as an auth bearer header. + type: http + scheme: bearer + bearerFormat: JWT schemas: UserRoles: description: The role (for permissions) of the user. diff --git a/backend/pine/backend/app.py b/backend/pine/backend/app.py index aa85c65..d7dd45f 100644 --- a/backend/pine/backend/app.py +++ b/backend/pine/backend/app.py @@ -7,7 +7,7 @@ import sys from . import log log.setup_logging() -from flask import Flask, abort, jsonify +from flask import Flask, abort, jsonify, redirect, render_template, request, send_file from flask import __version__ as flask_version from werkzeug import exceptions @@ -38,7 +38,7 @@ def handle_uncaught_exception(e): def create_app(test_config = None): # create and configure the app - app = Flask(__name__, instance_relative_config=True) + app = Flask(__name__, instance_relative_config=True, template_folder="api/swagger-ui") app.config.from_object(config) app.register_error_handler(exceptions.HTTPException, handle_error) @@ -78,6 +78,28 @@ def create_app(test_config = None): LOGGER.info(about) return jsonify(about) + @app.route("/openapi.yaml", methods=["GET"]) + def openapi_spec(): + # Specify statically where the openapi file is, relative path + return send_file("api/openapi.yaml", mimetype='text/yaml', as_attachment=False) + + @app.route("/swagger", methods=["GET"], strict_slashes=False) + def swagger_ui_index(): + # forward to /api/ui/index.html, taking proxy prefix into account if set + url = request.headers.get("X-Forwarded-Prefix", "") + "/swagger/index.html" + LOGGER.info("Redirecting to {}".format(url)) + return redirect(url) + + @app.route("/swagger/", methods=["GET"]) + def swagger_ui(file: str): + if file == "index.html": + # get url for /api/openapi.yaml, taking proxy prefix into account if set + url = request.headers.get("X-Forwarded-Prefix", "") + "/openapi.yaml" + LOGGER.info("Grabbing spec from {}".format(url)) + return render_template("index.html", spec_url=url) + else: + return send_file("api/swagger-ui/{}".format(file)) + from . import cors cors.init_app(app) @@ -105,7 +127,13 @@ def create_app(test_config = None): from .pineiaa import bp as iaabp iaabp.init_app(app) - from .api import bp as apibp - apibp.init_app(app) + if app.env == "development": + # if running dev stack, map /api/ to + for rule in app.url_map.iter_rules(): + if rule.rule.startswith("/api"): + continue + rule_copy = rule.empty() + rule_copy.rule = "/api" + rule_copy.rule + app.url_map.add(rule_copy) return app diff --git a/backend/pine/backend/auth/bp.py b/backend/pine/backend/auth/bp.py index 52a980c..941e078 100644 --- a/backend/pine/backend/auth/bp.py +++ b/backend/pine/backend/auth/bp.py @@ -61,7 +61,9 @@ def flask_get_logged_in_user() -> Response: @bp.route("/logged_in_user_details", methods = ["GET"]) @login_required def flask_get_logged_in_user_details() -> Response: - return jsonify(module.get_logged_in_user_details().to_dict()) + details = module.get_logged_in_user_details() + if details != None: details = details.to_dict() + return jsonify(details) @bp.route("/login_form", methods = ["GET"]) def flask_get_login_form() -> Response: diff --git a/backend/pine/backend/auth/eve.py b/backend/pine/backend/auth/eve.py index a47f28e..ac4df7f 100644 --- a/backend/pine/backend/auth/eve.py +++ b/backend/pine/backend/auth/eve.py @@ -1,6 +1,9 @@ # (C) 2019 The Johns Hopkins University Applied Physics Laboratory LLC. +import logging + from flask import jsonify, request, Response, session +from flask_httpauth import HTTPBasicAuth from overrides import overrides from werkzeug import exceptions @@ -8,6 +11,10 @@ from . import bp, login_required, password from .. import log, models from ..data import users +logger = logging.getLogger(__name__) + +auth = HTTPBasicAuth() + class EveUser(models.AuthUser): def __init__(self, data): @@ -37,6 +44,26 @@ class EveUser(models.AuthUser): def get_details(self) -> models.UserDetails: return models.UserDetails(self.data["firstname"], self.data["lastname"], self.data["description"]) +@auth.verify_password +def eve_login(username: str, passwd: str): + if not username or not passwd: + return None + try: + user = users.get_user(username) + except exceptions.HTTPException: + try: + user = users.get_user_by_email(username) + if not user: + raise exceptions.Unauthorized(description = "User \"{}\" doesn't exist.".format(username)) + except exceptions.HTTPException: + raise exceptions.Unauthorized(description = "User \"{}\" doesn't exist.".format(username)) + if not "passwdhash" in user or not user["passwdhash"]: + raise exceptions.Unauthorized(description = "Your first-time password needs to be set by an administrator.") + if password.check_password(passwd, user["passwdhash"]): + return user + else: + return None + class EveModule(bp.AuthModule): def __init__(self, app, bp): @@ -85,30 +112,37 @@ class EveModule(bp.AuthModule): models.LoginFormField("password", "Password", models.LoginFormFieldType.PASSWORD) ], "Login") - def login(self) -> Response: - if not request.json or "username" not in request.json or "password" not in request.json: - raise exceptions.BadRequest(description = "Missing username and/or password.") - username = request.json["username"] - passwd = request.json["password"] - try: - user = users.get_user(username) - except exceptions.HTTPException: - try: - user = users.get_user_by_email(username) - if not user: - raise exceptions.Unauthorized(description = "User \"{}\" doesn't exist.".format(username)) - except exceptions.HTTPException: - raise exceptions.Unauthorized(description = "User \"{}\" doesn't exist.".format(username)) - if not "passwdhash" in user or not user["passwdhash"]: - raise exceptions.Unauthorized(description = "Your first-time password needs to be set by an administrator.") - valid = password.check_password(passwd, user["passwdhash"]) - if not valid: - raise exceptions.Unauthorized(description = "Incorrect password for user \"{}\".".format(username)) + def _set_user(self, user): session["auth"] = { "user": EveUser(user).to_dict(), "user_data": user } log.access_flask_login() + + @auth.login_required(optional=True) + @overrides + def get_logged_in_user(self): + # if user set in cookie, use that + user = super(EveModule, self).get_logged_in_user() + if user != None: + return user + + # otherwise check basic auth + user = auth.current_user() + if user != None: + logger.info("User has logged in via basic auth; setting session.") + self._set_user(user) + return super(EveModule, self).get_logged_in_user() + + def login(self) -> Response: + if not request.json or "username" not in request.json or "password" not in request.json: + raise exceptions.BadRequest(description = "Missing username and/or password.") + username = request.json["username"] + passwd = request.json["password"] + user = eve_login(username, passwd) + if user == None: + raise exceptions.Unauthorized(description = "Incorrect password for user \"{}\".".format(username)) + self._set_user(user) return jsonify(self.get_logged_in_user()) def get_all_users(self): diff --git a/backend/pine/backend/auth/oauth.py b/backend/pine/backend/auth/oauth.py index 6baaf50..6d68246 100644 --- a/backend/pine/backend/auth/oauth.py +++ b/backend/pine/backend/auth/oauth.py @@ -9,6 +9,7 @@ import typing from authlib.integrations.flask_client import OAuth from flask import current_app, jsonify, redirect, request, Response, session +from flask_httpauth import HTTPTokenAuth import jwt from overrides import overrides from werkzeug import exceptions @@ -17,6 +18,7 @@ from . import bp from .. import log, models LOGGER = logging.getLogger(__name__) +auth = HTTPTokenAuth(scheme="Bearer") class OAuthUser(models.AuthUser): @@ -61,6 +63,7 @@ class OAuthModule(bp.AuthModule): bp.route("/login", methods=["POST"])(self.login) bp.route("/authorize", methods=["GET"])(self.authorize_get) bp.route("/authorize", methods=["POST"])(self.authorize_post) + auth.verify_token_callback = lambda t: self.verify_token(t) @abc.abstractmethod def register_oauth(self, oauth, app): @@ -82,8 +85,23 @@ class OAuthModule(bp.AuthModule): def get_login_form(self) -> models.LoginForm: return models.LoginForm([], self.get_login_form_button_text()) - def make_user(self, decoded: dict) -> OAuthUser: - return OAuthUser(decoded, id_field="sub") + @auth.login_required(optional=True) + @overrides + def get_logged_in_user(self): + # if user set in cookie, use that + user = super(OAuthModule, self).get_logged_in_user() + if user != None: + return user + + # otherwise check bearer auth + ret = auth.current_user() + if ret != None: + (token, user) = ret + if user != None: + LOGGER.info("User has logged in via bearer auth; setting session.") + self._update_session(user, token) + + return super(OAuthModule, self).get_logged_in_user() def login(self) -> Response: if "return_to" in request.args: @@ -92,14 +110,13 @@ class OAuthModule(bp.AuthModule): redirect = self.app.authorize_redirect(response_type = "token") return jsonify(redirect.headers["Location"]) - def _authorize(self, authorization_response): - try: - token = self.app.fetch_access_token(authorization_response = authorization_response) - except Exception as e: - traceback.print_exc() - sys.stderr.flush() - raise exceptions.SecurityError(description = str(e)) - access_token = token["access_token"] + def make_user(self, decoded: dict) -> OAuthUser: + return OAuthUser(decoded, id_field="sub") + + def verify_token(self, access_token: str) -> OAuthUser: + if not access_token: + return None + LOGGER.info("Verifying token: \"%s\"", access_token) try: decoded = jwt.decode(access_token, options={"verify_signature": False}) decoded = jwt.decode(access_token, self.secret, audience=decoded["aud"], algorithms=self.algorithms) @@ -108,12 +125,25 @@ class OAuthModule(bp.AuthModule): sys.stderr.flush() raise exceptions.SecurityError(description = str(e)) LOGGER.info("Decoded and validated token: {}".format(decoded)) + return ({"access_token": access_token}, self.make_user(decoded)) + + def _update_session(self, user: OAuthUser, token: dict): session["auth"] = { - "user": self.make_user(decoded).to_dict(), - "user_data": decoded, + "user": user.to_dict(), + "user_data": user.data, "token": token } log.access_flask_login() + + def _authorize(self, authorization_response): + try: + token = self.app.fetch_access_token(authorization_response = authorization_response) + except Exception as e: + traceback.print_exc() + sys.stderr.flush() + raise exceptions.SecurityError(description = str(e)) + (_, user) = self.verify_token(token["access_token"]) + self._update_session(user, token) return jsonify(self.get_logged_in_user()) def authorize_post(self): diff --git a/backend/pine/backend/auth/openapi.yaml b/backend/pine/backend/auth/openapi.yaml index 2663fd5..e2770c6 100644 --- a/backend/pine/backend/auth/openapi.yaml +++ b/backend/pine/backend/auth/openapi.yaml @@ -67,10 +67,11 @@ components: description: An auth token obtained by Vegas out-of-band from PINE. type: object properties: - auth_token: + access_token: type: string token_type: type: string + example: bearer additionalProperties: {} paths: @@ -143,7 +144,14 @@ paths: If there is no user logged, in `null` is returned. Example: `curl -X GET http://localhost:5000/auth/logged_in_user --cookie session.cookie` + + Note that this operation does not REQUIRE the user to be logged in, but it is marked as + requiring security so that the Swagger UI will send in whatever you have configured. operationId: auth_logged_in_user + security: + - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: [auth] responses: "200": @@ -167,6 +175,8 @@ paths: operationId: auth_user_details security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: [auth] responses: "200": @@ -188,6 +198,7 @@ paths: operationId: auth_eve_update_user_details security: - cookieAuth: [] + - eveBasicAuth: [] tags: [auth_eve] requestBody: content: @@ -236,6 +247,8 @@ paths: tags: [auth] security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] responses: "200": description: Successfully logged out. @@ -302,6 +315,7 @@ paths: tags: [auth_eve] security: - cookieAuth: [] + - eveBasicAuth: [] responses: "200": description: Returns all users. @@ -325,6 +339,7 @@ paths: tags: [auth_eve] security: - cookieAuth: [] + - eveBasicAuth: [] requestBody: content: application/json: diff --git a/backend/pine/backend/collections/openapi.yaml b/backend/pine/backend/collections/openapi.yaml index f9ec1b2..28a2a5d 100644 --- a/backend/pine/backend/collections/openapi.yaml +++ b/backend/pine/backend/collections/openapi.yaml @@ -4,6 +4,8 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - name: collections diff --git a/backend/pine/backend/config.py b/backend/pine/backend/config.py index eb3bc15..5a8398c 100644 --- a/backend/pine/backend/config.py +++ b/backend/pine/backend/config.py @@ -4,7 +4,8 @@ import os # default configuration values -SECRET_KEY = "Cq13XII=%" +SECRET_KEY = os.environ.get("BACKEND_SECRET_KEY", "Cq13XII=%") +if not SECRET_KEY: SECRET_KEY = "Cq13XII=%" DEBUG = True if os.environ.get("EVE_SERVER"): diff --git a/backend/pine/backend/documents/openapi.yaml b/backend/pine/backend/documents/openapi.yaml index ddb0846..7ae4bdc 100644 --- a/backend/pine/backend/documents/openapi.yaml +++ b/backend/pine/backend/documents/openapi.yaml @@ -4,6 +4,8 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - name: documents diff --git a/backend/pine/backend/pineiaa/openapi.yaml b/backend/pine/backend/pineiaa/openapi.yaml index a2a8c24..3547af8 100644 --- a/backend/pine/backend/pineiaa/openapi.yaml +++ b/backend/pine/backend/pineiaa/openapi.yaml @@ -4,6 +4,8 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - name: iaa_reports diff --git a/backend/pine/backend/pipelines/openapi.yaml b/backend/pine/backend/pipelines/openapi.yaml index b4d3caf..a9e258d 100644 --- a/backend/pine/backend/pipelines/openapi.yaml +++ b/backend/pine/backend/pipelines/openapi.yaml @@ -4,6 +4,8 @@ openapi: "3.0.2" security: - cookieAuth: [] + - eveBasicAuth: [] + - vegasBearerAuth: [] tags: - name: pipelines diff --git a/docker-compose.yml b/docker-compose.yml index 26fa2b4..7b64356 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,6 +56,7 @@ services: PINE_LOGGING_CONFIG_FILE: /nlp-web-app/shared/logging.python.json DOCUMENT_IMAGE_DIR: /mnt/azure PINE_VERSION: ${PINE_VERSION:?Please set PINE_VERSION environment variable.} + BACKEND_SECRET_KEY: ${BACKEND_SECRET_KEY} # Expose the following to test: # ports: # - ${BACKEND_PORT}:${BACKEND_PORT} diff --git a/eve/python/settings.py b/eve/python/settings.py index 9c1da62..a53c135 100755 --- a/eve/python/settings.py +++ b/eve/python/settings.py @@ -185,7 +185,7 @@ DOMAIN={ if os.environ.get("MONGO_URI"): MONGO_URI = os.environ.get("MONGO_URI") - #LOGGER.info("Eve using MONGO_URI={}".format(MONGO_URI)) + LOGGER.info("Eve using externally configured MONGO_URI") else: MONGO_HOST = "localhost" MONGO_PORT = int(os.environ.get("MONGO_PORT", 27017)) diff --git a/frontend/annotation/nginx/nlp-web-app.http.mustache b/frontend/annotation/nginx/nlp-web-app.http.mustache index 5f6ab64..8c4f5dc 100644 --- a/frontend/annotation/nginx/nlp-web-app.http.mustache +++ b/frontend/annotation/nginx/nlp-web-app.http.mustache @@ -16,10 +16,13 @@ server { try_files $uri$args $uri$args/ $uri $uri/ /index.html =404; } + include snippets/api-rules.conf; + location /api { include proxy_params; proxy_set_header X-Forwarded-Prefix /api; rewrite ^/api(.*) $1 break; proxy_pass ${BACKEND_SERVER}; } + } diff --git a/frontend/annotation/nginx/nlp-web-app.https.mustache b/frontend/annotation/nginx/nlp-web-app.https.mustache index a4fccc0..94e78b0 100644 --- a/frontend/annotation/nginx/nlp-web-app.https.mustache +++ b/frontend/annotation/nginx/nlp-web-app.https.mustache @@ -18,6 +18,8 @@ server { try_files $uri$args $uri$args/ $uri $uri/ /index.html =404; } + include snippets/api-rules.conf; + location /api { include proxy_params; proxy_set_header X-Forwarded-Prefix /api; @@ -25,6 +27,7 @@ server { proxy_pass ${BACKEND_SERVER}; proxy_redirect http://$http_host/ https://$http_host/; } + } server { diff --git a/frontend/annotation/nginx/snippets/api-rules.conf b/frontend/annotation/nginx/snippets/api-rules.conf new file mode 100644 index 0000000..250e2a0 --- /dev/null +++ b/frontend/annotation/nginx/snippets/api-rules.conf @@ -0,0 +1,6 @@ +# special cases for redirecting inside api module +# the X-Forwarded-Prefix header doesn't work if this nginx is behind another nginx +rewrite ^/openapi.yaml?$ /api/openapi.yaml; +rewrite ^/swagger?$ /api/swagger; +rewrite ^/swagger/?$ /api/swagger/; +rewrite ^/swagger/(.+)?$ /api/swagger/$1;